1
Fork 0
mirror of https://github.com/diced/zipline.git synced 2025-04-04 23:21:17 -05:00
This commit is contained in:
dicedtomatoreal 2020-10-05 14:50:32 -07:00
parent 7d68dc78eb
commit 06777cca92
28 changed files with 1040 additions and 603 deletions

View file

@ -1,10 +1,9 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
title: ""
labels: bug
assignees: dicedtomatoreal
---
**Describe the bug**
@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@ -24,9 +24,10 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Arch]
- Browser [e.g. chrome, firefox, chrome mobile]
- Version [e.g. 2.0.0]
- OS: [e.g. Arch]
- Browser [e.g. chrome, firefox, chrome mobile]
- Version [e.g. 2.0.0]
**Additional context**
Add any other context about the problem here.

View file

@ -5,13 +5,12 @@ name: Node.js CI
on:
push:
branches: [ master ]
branches: [master]
pull_request:
branches: [ master ]
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
strategy:
@ -19,10 +18,10 @@ jobs:
node-version: [14.x]
steps:
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build --if-present
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build --if-present

4
.prettierignore Normal file
View file

@ -0,0 +1,4 @@
dist
scripts
public
views

1
.prettierrc.json Normal file
View file

@ -0,0 +1 @@
{}

View file

@ -14,22 +14,22 @@ appearance, race, religion, or sexual identity and orientation.
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities

View file

@ -155,26 +155,26 @@ Every single configuration option will be listed here
| `upload.fileLength` | integer | how long the random id for a file should be |
| `upload.tempDir` | string | temporary directory, files are stored here and then deleted. |
| `upload.uploadDir` | string | upload directory (where all uploads are stored) |
| `upload.route` | string | Route for uploads, default is /u, ex.`/u/hd27ua.png` |
| `upload.route` | string | Route for uploads, default is /u, ex.`/u/hd27ua.png` |
#### User Settings
**Config Property:** `user`
| Config Property | Type | Description / Expected Values |
| ------------------- | ------- | ------------------------------------------------------------ |
| `user.tokenLength` | integer | How long the randomly generated user token should be |
| Config Property | Type | Description / Expected Values |
| ------------------ | ------- | ---------------------------------------------------- |
| `user.tokenLength` | integer | How long the randomly generated user token should be |
#### Site Settings
**Config Property:** `site`
| Config Property | Type | Description / Expected Values |
| --------------- | ------- | ------------------------------------------------------ |
| `site.protocol` | integer | protocol (http or https) |
| `site.serveHTTP` | string | Port to run the web server on with HTTP (can be used with nginx + CloudFlare as a reverse proxy and let CloudFlare take care of SSL) |
| `site.serveHTTPS` | string | Port to run the web server on with HTTPS (only will be used if `site.protocol` is `https`) (you will need SSL certificates! See [this](#site-ssl-settings)) |
| `site.logRoutes` | boolean | Wether or not to log routes when they are requested |
| Config Property | Type | Description / Expected Values |
| ----------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `site.protocol` | integer | protocol (http or https) |
| `site.serveHTTP` | string | Port to run the web server on with HTTP (can be used with nginx + CloudFlare as a reverse proxy and let CloudFlare take care of SSL) |
| `site.serveHTTPS` | string | Port to run the web server on with HTTPS (only will be used if `site.protocol` is `https`) (you will need SSL certificates! See [this](#site-ssl-settings)) |
| `site.logRoutes` | boolean | Wether or not to log routes when they are requested |
#### Site SSL Settings
@ -182,16 +182,16 @@ Every single configuration option will be listed here
| Config Property | Type | Description / Expected Values |
| --------------- | ------ | ----------------------------------------------- |
| `site.ssl.key` | string | path to ssl private key. ex: `./ssl/server.key` |
| `site.ssl.cert` | string | path to ssl certificate. ex: `./ssl/cert.crt` |
| `site.ssl.key` | string | path to ssl private key. ex: `./ssl/server.key` |
| `site.ssl.cert` | string | path to ssl certificate. ex: `./ssl/cert.crt` |
#### Administrator User
**Config Property:** `administrator`
| Config Property | Type | Description / Expected Values |
| ----------------------------- | ------ | -------------------------------------------------------------------------------------------------------- |
| `administrator.password` | string | password of administrator user (NOT RECOMENDED to use administrator user, set this to a SECURE password) |
| Config Property | Type | Description / Expected Values |
| ------------------------ | ------ | -------------------------------------------------------------------------------------------------------- |
| `administrator.password` | string | password of administrator user (NOT RECOMENDED to use administrator user, set this to a SECURE password) |
#### Database Configuration
@ -232,63 +232,60 @@ Particles.JS, can be enabled and it's config can be changed willingly.
| `meta.favicon` | string | has to be in /public/assets folder and should be formatted as `"/public/assets/<file name>"` |
| `meta.title` | string | title of your server shows up like `<title> - Login` or `<title> - Dashboard` |
### Example Config
```json
{
"upload": {
"fileLength": 6,
"tempDir": "./temp",
"uploadDir": "./uploads",
"route": "/u"
"upload": {
"fileLength": 6,
"tempDir": "./temp",
"uploadDir": "./uploads",
"route": "/u"
},
"shorten": {
"idLength": 4,
"route": "/s"
},
"user": {
"tokenLength": 32
},
"site": {
"protocol": "http",
"returnProtocol": "https",
"ssl": {
"key": "./ssl/server.key",
"cert": "./ssl/server.crt"
},
"shorten": {
"idLength": 4,
"route": "/s"
},
"user": {
"tokenLength": 32
},
"site": {
"protocol": "http",
"returnProtocol": "https",
"ssl": {
"key": "./ssl/server.key",
"cert": "./ssl/server.crt"
},
"serveHTTPS": 8000,
"serveHTTP": 443,
"logRoutes": true
},
"administrator": {
"password": "1234"
},
"orm": {
"type": "postgres",
"host": "localhost",
"port": 5432,
"username": "user",
"password": "1234",
"database": "typex",
"synchronize": true,
"logging": false,
"entities": [
"out/src/entities/**/*.js"
]
},
"sessionSecret": "1234",
"saltRounds": 10, // You might get an error if its over a certain number, so choose carefully.
"meta": {
"favicon": "/public/assets/typex_small_circle.png",
"title": "TypeX"
},
"discordWebhook": {
"enabled": true,
"url": "https://canary.discordapp.com/api/webhooks/id/token",
"username": "TypeX Logs",
"avatarURL": "https://domain/public/assets/typex_small_circle.png"
}
"serveHTTPS": 8000,
"serveHTTP": 443,
"logRoutes": true
},
"administrator": {
"password": "1234"
},
"orm": {
"type": "postgres",
"host": "localhost",
"port": 5432,
"username": "user",
"password": "1234",
"database": "typex",
"synchronize": true,
"logging": false,
"entities": ["out/src/entities/**/*.js"]
},
"sessionSecret": "1234",
"saltRounds": 10, // You might get an error if its over a certain number, so choose carefully.
"meta": {
"favicon": "/public/assets/typex_small_circle.png",
"title": "TypeX"
},
"discordWebhook": {
"enabled": true,
"url": "https://canary.discordapp.com/api/webhooks/id/token",
"username": "TypeX Logs",
"avatarURL": "https://domain/public/assets/typex_small_circle.png"
}
}
```
@ -315,16 +312,17 @@ These are the options you must pass when uploading a url or image/file
### Uploader
| Property | Value |
|-----------|-------------------------------------|
| --------- | ----------------------------------- |
| URL | `https://<DOMAIN>/api/upload` |
| Header | `authorization: <TOKEN>` |
| Header | `content-type: multipart/form-data` |
| File name | `file` |
### URL Shortener
| Property | Value |
|-----------|-------------------------------------|
| URL | `https://<DOMAIN>/api/shorten` |
| Header | `authorization: <TOKEN>` |
| Header | `content-type: application/json` |
| Data | `{"url": "<URL>"}` |
| Property | Value |
| -------- | -------------------------------- |
| URL | `https://<DOMAIN>/api/shorten` |
| Header | `authorization: <TOKEN>` |
| Header | `content-type: application/json` |
| Data | `{"url": "<URL>"}` |

View file

@ -1,7 +1,9 @@
# Zipline
Zipline is a Typescript based image/file uploading service & URL shortening service! Simple, and elegant.
# Images
![](https://cdn.diced.wtf/u/F1vtRX.png)
![](https://cdn.diced.wtf/u/a5BTaP.png)
![](https://cdn.diced.wtf/u/bdntjm.png)
@ -11,7 +13,9 @@ Zipline is a Typescript based image/file uploading service & URL shortening serv
![](https://cdn.diced.wtf/u/VTXMbo.png)
# Bugs?
Make sure to open an issue if you think you ran into an issue, or need help with anything.
# Where is all the old information here?
All configuration options have been moved to the wiki, as the README was getting too long and hard to manage.

8
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "typex",
"version": "2.1.3",
"version": "2.1.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -1420,6 +1420,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-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",

View file

@ -30,6 +30,7 @@
},
"devDependencies": {
"mongodb": "^3.5.8",
"pg": "^8.0.3"
"pg": "^8.0.3",
"prettier": "2.1.2"
}
}

View file

@ -1,211 +1,426 @@
import { BAD_REQUEST, FORBIDDEN } from 'http-status-codes';
import { Controller, Middleware, Get, Post, Delete, Patch } from '@overnightjs/core';
import { Request, Response } from 'express';
import { ORMHandler } from '..';
import { randomId, getImage, findFile, getShorten, hashPassword } from '../util';
import { createReadStream, createWriteStream, unlinkSync, existsSync, mkdirSync, readFileSync } from 'fs'
import { User } from '../entities/User';
import { sep } from 'path';
import { cookiesForAPI } from '../middleware/cookiesForAPI';
import { DiscordWebhook } from '../structures/DiscordWebhook';
import { ImageUtil } from '../structures/ImageUtil';
import { ShortenUtil } from '../structures/ShortenUtil';
import { BAD_REQUEST, FORBIDDEN } from "http-status-codes";
import {
Controller,
Middleware,
Get,
Post,
Delete,
Patch,
} from "@overnightjs/core";
import { Request, Response } from "express";
import { ORMHandler } from "..";
import {
randomId,
getImage,
findFile,
getShorten,
hashPassword,
} from "../util";
import {
createReadStream,
createWriteStream,
unlinkSync,
existsSync,
mkdirSync,
readFileSync,
} from "fs";
import { User } from "../entities/User";
import { sep } from "path";
import { cookiesForAPI } from "../middleware/cookiesForAPI";
import { DiscordWebhook } from "../structures/DiscordWebhook";
import { ImageUtil } from "../structures/ImageUtil";
import { ShortenUtil } from "../structures/ShortenUtil";
import { Note } from "../entities/Note";
import Logger from '@ayanaware/logger';
import multer from 'multer'
import Logger from "@ayanaware/logger";
import multer from "multer";
if (!findFile('config.json', process.cwd())) {
Logger.get('FS').error(`No config.json exists in the ${__dirname}, exiting...`)
if (!findFile("config.json", process.cwd())) {
Logger.get("FS").error(
`No config.json exists in the ${__dirname}, exiting...`
);
process.exit(1);
}
const config = JSON.parse(readFileSync(findFile('config.json', process.cwd()), 'utf8'))
const config = JSON.parse(
readFileSync(findFile("config.json", process.cwd()), "utf8")
);
const upload = multer({ dest: config.uploader.temp });
@Controller('api')
@Controller("api")
export class APIController {
public orm: ORMHandler;
@Post('upload')
@Middleware(upload.single('file'))
@Post("upload")
@Middleware(upload.single("file"))
private async upload(req: Request, res: Response) {
const users = await this.orm.repos.user.find({ where: { token: req.headers['authorization'] } });
if (!users[0]) return res.status(FORBIDDEN).json({ code: FORBIDDEN, message: "Unauthorized" })
if (req.headers['authorization'] !== users[0].token) return res.status(FORBIDDEN).json({ code: FORBIDDEN, message: "Unauthorized" })
const users = await this.orm.repos.user.find({
where: { token: req.headers["authorization"] },
});
if (!users[0])
return res
.status(FORBIDDEN)
.json({ code: FORBIDDEN, message: "Unauthorized" });
if (req.headers["authorization"] !== users[0].token)
return res
.status(FORBIDDEN)
.json({ code: FORBIDDEN, message: "Unauthorized" });
const user = users[0];
const id = randomId(config.uploader.length);
if (config.uploader.blacklistedExt.includes(req.file.originalname.split('.').pop())) return res.status(BAD_REQUEST).json({ code: BAD_REQUEST, message: 'The extension used in this file is blacklisted.' })
if (
config.uploader.blacklistedExt.includes(
req.file.originalname.split(".").pop()
)
)
return res
.status(BAD_REQUEST)
.json({
code: BAD_REQUEST,
message: "The extension used in this file is blacklisted.",
});
const source = createReadStream(req.file.path);
if (!existsSync(config.uploader.upload)) mkdirSync(config.uploader.upload);
const destination = createWriteStream(`${config.uploader.upload}${sep}${id}.${req.file.originalname.split('.').pop()}`);
const destination = createWriteStream(
`${config.uploader.upload}${sep}${id}.${req.file.originalname
.split(".")
.pop()}`
);
source.pipe(destination, { end: false });
source.on("end", function () {
unlinkSync(req.file.path);
});
const img = await getImage(this.orm, `${req.protocol}://${req.headers['host']}${config.uploader.route}/${id}.${req.file.originalname.split('.').pop()}`, user.id)
Logger.get('TypeX.Uploader').info(`New image uploaded ${img.url} (${img.id}) by ${user.username} (${user.id})`)
if (config.discordWebhook.enabled) new DiscordWebhook(config.discordWebhook.url).sendImageUpdate(user, ImageUtil.parseURL(img.url), config);
return res.status(200).send(`${req.protocol}://${req.headers['host']}${config.uploader.route}/${id}.${req.file.originalname.split('.').pop()}`)
const img = await getImage(
this.orm,
`${req.protocol}://${req.headers["host"]}${
config.uploader.route
}/${id}.${req.file.originalname.split(".").pop()}`,
user.id
);
Logger.get("TypeX.Uploader").info(
`New image uploaded ${img.url} (${img.id}) by ${user.username} (${user.id})`
);
if (config.discordWebhook.enabled)
new DiscordWebhook(config.discordWebhook.url).sendImageUpdate(
user,
ImageUtil.parseURL(img.url),
config
);
return res
.status(200)
.send(
`${req.protocol}://${req.headers["host"]}${
config.uploader.route
}/${id}.${req.file.originalname.split(".").pop()}`
);
}
@Post('shorten')
@Post("shorten")
private async shorten(req: Request, res: Response) {
const users = await this.orm.repos.user.find({ where: { token: req.headers['authorization'] } });
if (!users[0]) return res.status(FORBIDDEN).json({ code: FORBIDDEN, message: "Unauthorized" })
if (req.headers['authorization'] !== users[0].token) return res.status(FORBIDDEN).json({ code: FORBIDDEN, message: "Unauthorized" })
const users = await this.orm.repos.user.find({
where: { token: req.headers["authorization"] },
});
if (!users[0])
return res
.status(FORBIDDEN)
.json({ code: FORBIDDEN, message: "Unauthorized" });
if (req.headers["authorization"] !== users[0].token)
return res
.status(FORBIDDEN)
.json({ code: FORBIDDEN, message: "Unauthorized" });
const user = users[0];
const id = randomId(config.shortener.length)
const shrt = await getShorten(this.orm, id, req.body.url, `${req.protocol}://${req.headers['host']}${config.shortener.route}/${id}`, user.id);
Logger.get('TypeX.Shortener').info(`New url shortened ${shrt.url} (${req.body.url}) (${shrt.id}) by ${user.username} (${user.id})`)
if (config.discordWebhook.enabled) new DiscordWebhook(config.discordWebhook.url).sendShortenUpdate(user, shrt, ShortenUtil.parseURL(shrt.url), config);
return res.status(200).send(`${req.protocol}://${req.headers['host']}${config.shortener.route}/${id}`)
const id = randomId(config.shortener.length);
const shrt = await getShorten(
this.orm,
id,
req.body.url,
`${req.protocol}://${req.headers["host"]}${config.shortener.route}/${id}`,
user.id
);
Logger.get("TypeX.Shortener").info(
`New url shortened ${shrt.url} (${req.body.url}) (${shrt.id}) by ${user.username} (${user.id})`
);
if (config.discordWebhook.enabled)
new DiscordWebhook(config.discordWebhook.url).sendShortenUpdate(
user,
shrt,
ShortenUtil.parseURL(shrt.url),
config
);
return res
.status(200)
.send(
`${req.protocol}://${req.headers["host"]}${config.shortener.route}/${id}`
);
}
@Post('note')
@Post("note")
private async note(req: Request, res: Response) {
const users = await this.orm.repos.user.find({ where: { token: req.headers['authorization'] } });
if (!users[0]) return res.status(FORBIDDEN).json({ code: FORBIDDEN, message: "Unauthorized" })
if (req.headers['authorization'] !== users[0].token) return res.status(FORBIDDEN).json({ code: FORBIDDEN, message: "Unauthorized" })
const users = await this.orm.repos.user.find({
where: { token: req.headers["authorization"] },
});
if (!users[0])
return res
.status(FORBIDDEN)
.json({ code: FORBIDDEN, message: "Unauthorized" });
if (req.headers["authorization"] !== users[0].token)
return res
.status(FORBIDDEN)
.json({ code: FORBIDDEN, message: "Unauthorized" });
const user = users[0];
const id = randomId(config.notes.length)
const note = await this.orm.repos.note.save(new Note().set({
key: id,
user: user.id,
content: req.body.content,
expiration: req.body.expiration ? req.body.expiration : null
}));
Logger.get('TypeX.Notes').info(`New note created ${note.id} and ${note.expriation ? `will expire in ${note.expriation},` : `will not expire,`} by ${user.username} (${user.id})`)
const id = randomId(config.notes.length);
const note = await this.orm.repos.note.save(
new Note().set({
key: id,
user: user.id,
content: req.body.content,
expiration: req.body.expiration ? req.body.expiration : null,
})
);
Logger.get("TypeX.Notes").info(
`New note created ${note.id} and ${
note.expriation
? `will expire in ${note.expriation},`
: `will not expire,`
} by ${user.username} (${user.id})`
);
// if (config.discordWebhook.enabled) new DiscordWebhook(config.discordWebhook.url).sendShortenUpdate(user, shrt, ShortenUtil.parseURL(shrt.url), config);
return res.status(200).send(`${req.protocol}://${req.headers['host']}${config.notes.route}/${id}`)
return res
.status(200)
.send(
`${req.protocol}://${req.headers["host"]}${config.notes.route}/${id}`
);
}
@Get('users')
@Get("users")
@Middleware(cookiesForAPI)
private async getUsers(req: Request, res: Response) {
if (!req.session.user.administrator) return res.status(FORBIDDEN).json({ code: FORBIDDEN, message: 'Unauthorized' });
if (!req.session.user.administrator)
return res
.status(FORBIDDEN)
.json({ code: FORBIDDEN, message: "Unauthorized" });
try {
let users = await this.orm.repos.user.find({ order: { id: 'ASC' } })
let users = await this.orm.repos.user.find({ order: { id: "ASC" } });
return res.status(200).json(users);
} catch (e) {
return res.status(BAD_REQUEST).json({ error: "Could not create user: " + e.message })
return res
.status(BAD_REQUEST)
.json({ error: "Could not create user: " + e.message });
}
}
@Post('users')
@Post("users")
@Middleware(cookiesForAPI)
private async createUser(req: Request, res: Response) {
if (!req.session.user.administrator) return res.status(FORBIDDEN).json({ code: FORBIDDEN, message: 'Unauthorized' });
if (!req.session.user.administrator)
return res
.status(FORBIDDEN)
.json({ code: FORBIDDEN, message: "Unauthorized" });
const data = req.body;
try {
let user = await this.orm.repos.user.findOne({ username: data.username })
if (user) return res.status(BAD_REQUEST).json({ error: "Could not create user: user exists already" })
user = await this.orm.repos.user.save(new User().set({ username: data.username, password: hashPassword(data.password, config.core.saltRounds), administrator: data.administrator }))
Logger.get('TypeX.User.Create').info(`User ${user.username} (${user.id}) was created`)
let user = await this.orm.repos.user.findOne({ username: data.username });
if (user)
return res
.status(BAD_REQUEST)
.json({ error: "Could not create user: user exists already" });
user = await this.orm.repos.user.save(
new User().set({
username: data.username,
password: hashPassword(data.password, config.core.saltRounds),
administrator: data.administrator,
})
);
Logger.get("TypeX.User.Create").info(
`User ${user.username} (${user.id}) was created`
);
return res.status(200).json(user);
} catch (e) {
return res.status(BAD_REQUEST).json({ error: "Could not create user: " + e.message })
return res
.status(BAD_REQUEST)
.json({ error: "Could not create user: " + e.message });
}
}
@Post('users/register')
@Post("users/register")
private async registerUser(req: Request, res: Response) {
const data = req.body;
if (!config.core.public) return res.status(BAD_REQUEST).json({ error: 'This zipline server does not have public enabled, therefore can\'t create a user.' });
if (!config.core.public)
return res
.status(BAD_REQUEST)
.json({
error:
"This zipline server does not have public enabled, therefore can't create a user.",
});
try {
let user = await this.orm.repos.user.findOne({ username: data.username })
if (user) return res.status(BAD_REQUEST).json({ error: "Could not create user: user exists already" });
user = await this.orm.repos.user.save(new User().set({ username: data.username, password: hashPassword(data.password, config.core.saltRounds), administrator: false }));
let user = await this.orm.repos.user.findOne({ username: data.username });
if (user)
return res
.status(BAD_REQUEST)
.json({ error: "Could not create user: user exists already" });
user = await this.orm.repos.user.save(
new User().set({
username: data.username,
password: hashPassword(data.password, config.core.saltRounds),
administrator: false,
})
);
return res.status(200).json({ success: true });
} catch (e) {
return res.status(BAD_REQUEST).json({ error: `Couldn't create user: ${e.message}` })
}
return res
.status(BAD_REQUEST)
.json({ error: `Couldn't create user: ${e.message}` });
}
}
@Patch('users/:id')
@Patch("users/:id")
@Middleware(cookiesForAPI)
private async patchUser(req: Request, res: Response) {
const data = req.body;
if (!data.payload) return res.status(FORBIDDEN).json({ code: BAD_REQUEST, message: 'No payload specified.' });
if (data.payload === 'USER_EDIT') {
if (Number(req.params.id) !== Number(req.session.user.id)) return res.status(FORBIDDEN).json({ code: FORBIDDEN, message: 'Unauthorized' });
data.password = hashPassword(data.password, config.core.saltRounds)
if (!data.payload)
return res
.status(FORBIDDEN)
.json({ code: BAD_REQUEST, message: "No payload specified." });
if (data.payload === "USER_EDIT") {
if (Number(req.params.id) !== Number(req.session.user.id))
return res
.status(FORBIDDEN)
.json({ code: FORBIDDEN, message: "Unauthorized" });
data.password = hashPassword(data.password, config.core.saltRounds);
try {
let user = await this.orm.repos.user.findOne({ id: Number(req.params.id) })
if (!user) return res.status(BAD_REQUEST).json({ error: "Could not edit user: user doesnt exist" })
this.orm.repos.user.update({ id: Number(req.params.id) }, { username: data.username, password: data.password });
Logger.get('TypeX.User.Edit').info(`User ${user.username} (${user.id}) was edited`)
let user = await this.orm.repos.user.findOne({
id: Number(req.params.id),
});
if (!user)
return res
.status(BAD_REQUEST)
.json({ error: "Could not edit user: user doesnt exist" });
this.orm.repos.user.update(
{ id: Number(req.params.id) },
{ username: data.username, password: data.password }
);
Logger.get("TypeX.User.Edit").info(
`User ${user.username} (${user.id}) was edited`
);
return res.status(200).json(user);
} catch (e) {
return res.status(BAD_REQUEST).json({ error: "Could not edit user: " + e.message })
return res
.status(BAD_REQUEST)
.json({ error: "Could not edit user: " + e.message });
}
} else if (data.payload === 'USER_RESET_PASSWORD') {
data.password = hashPassword(data.password, config.core.saltRounds)
} else if (data.payload === "USER_RESET_PASSWORD") {
data.password = hashPassword(data.password, config.core.saltRounds);
try {
let user = await this.orm.repos.user.findOne({ id: Number(req.params.id) })
if (!user) return res.status(BAD_REQUEST).json({ error: "Could not reset password: user doesnt exist" })
this.orm.repos.user.update({ id: Number(req.params.id) }, { password: data.password });
Logger.get('TypeX.User.Edit.ResetPassword').info(`User ${user.username} (${user.id}) had their password reset.`)
let user = await this.orm.repos.user.findOne({
id: Number(req.params.id),
});
if (!user)
return res
.status(BAD_REQUEST)
.json({ error: "Could not reset password: user doesnt exist" });
this.orm.repos.user.update(
{ id: Number(req.params.id) },
{ password: data.password }
);
Logger.get("TypeX.User.Edit.ResetPassword").info(
`User ${user.username} (${user.id}) had their password reset.`
);
return res.status(200).json(user);
} catch (e) {
return res.status(BAD_REQUEST).json({ error: "Could not reset password: " + e.message })
return res
.status(BAD_REQUEST)
.json({ error: "Could not reset password: " + e.message });
}
} else if (data.payload === 'USER_TOKEN_RESET') {
} else if (data.payload === "USER_TOKEN_RESET") {
try {
let user = await this.orm.repos.user.findOne({ id: req.session.user.id })
if (!user) return res.status(BAD_REQUEST).json({ error: "Could not regen token: user doesnt exist" })
let user = await this.orm.repos.user.findOne({
id: req.session.user.id,
});
if (!user)
return res
.status(BAD_REQUEST)
.json({ error: "Could not regen token: user doesnt exist" });
user.token = randomId(config.core.userTokenLength);
req.session.user = user;
await this.orm.repos.user.save(user);
Logger.get('TypeX.User.Token').info(`User ${user.username} (${user.id}) token was regenerated`)
Logger.get("TypeX.User.Token").info(
`User ${user.username} (${user.id}) token was regenerated`
);
return res.status(200).json(user);
} catch (e) {
return res.status(BAD_REQUEST).json({ error: "Could not regen token: " + e.message })
return res
.status(BAD_REQUEST)
.json({ error: "Could not regen token: " + e.message });
}
} else {
console.log(data);
}
}
@Delete('users/:id')
@Delete("users/:id")
@Middleware(cookiesForAPI)
private async deleteUser(req: Request, res: Response) {
if (!req.session.user.administrator) return res.status(FORBIDDEN).json({ code: FORBIDDEN, message: 'Unauthorized' });
if (!req.session.user.administrator)
return res
.status(FORBIDDEN)
.json({ code: FORBIDDEN, message: "Unauthorized" });
try {
let user = await this.orm.repos.user.findOne({ id: Number(req.params.id) })
if (!user) return res.status(BAD_REQUEST).json({ error: "Could not delete user: user doesnt exist" })
let user = await this.orm.repos.user.findOne({
id: Number(req.params.id),
});
if (!user)
return res
.status(BAD_REQUEST)
.json({ error: "Could not delete user: user doesnt exist" });
this.orm.repos.user.delete({ id: Number(req.params.id) });
Logger.get('TypeX.User.Delete').info(`User ${user.username} (${user.id}) was deleted`)
Logger.get("TypeX.User.Delete").info(
`User ${user.username} (${user.id}) was deleted`
);
return res.status(200).json(user);
} catch (e) {
return res.status(BAD_REQUEST).json({ error: "Could not delete user: " + e.message })
return res
.status(BAD_REQUEST)
.json({ error: "Could not delete user: " + e.message });
}
}
@Get('images')
@Get("images")
@Middleware(cookiesForAPI)
private async allImages(req: Request, res: Response) {
const all = await this.orm.repos.image.find({ where: { user: req.session.user.id }, order: { id: 'ASC' } });
const all = await this.orm.repos.image.find({
where: { user: req.session.user.id },
order: { id: "ASC" },
});
return res.status(200).json(all);
}
@Get('images/statistics')
@Get("images/statistics")
@Middleware(cookiesForAPI)
private async statistics(req: Request, res: Response) {
const all = await this.orm.repos.image.find({ where: { user: req.session.user.id }, order: { id: 'ASC' } });
const totalViews = all.map(i => i.views).length !== 0 ? all.map(i => i.views).reduce((a, b) => Number(a) + Number(b)) : 0
const all = await this.orm.repos.image.find({
where: { user: req.session.user.id },
order: { id: "ASC" },
});
const totalViews =
all.map((i) => i.views).length !== 0
? all.map((i) => i.views).reduce((a, b) => Number(a) + Number(b))
: 0;
const users = await this.orm.repos.user.find();
const images = [];
const views = [];
for (const user of users) {
const i = await this.orm.repos.image.find({ where: { user: user.id }, order: { views: 'ASC' } });
const i = await this.orm.repos.image.find({
where: { user: user.id },
order: { views: "ASC" },
});
images.push({
username: user.username,
count: i.length
count: i.length,
});
views.push({
username: user.username,
count: i.map(i => i.views).length !== 0 ? i.map(i => i.views).reduce((a, b) => Number(a) + Number(b)) : 0
})
count:
i.map((i) => i.views).length !== 0
? i.map((i) => i.views).reduce((a, b) => Number(a) + Number(b))
: 0,
});
}
return res.status(200).json({
totalViews,
@ -213,39 +428,57 @@ export class APIController {
average: totalViews / all.length,
table: {
images: images.sort((a, b) => b.count - a.count),
views: views.sort((a, b) => b.count - a.count)
}
views: views.sort((a, b) => b.count - a.count),
},
});
}
@Delete('images/:id')
@Delete("images/:id")
@Middleware(cookiesForAPI)
private async deleteImage(req: Request, res: Response) {
try {
let image = await this.orm.repos.image.findOne({ id: Number(req.params.id) })
if (!image) return res.status(BAD_REQUEST).json({ error: "Could not delete image: image doesnt exist in database" })
let image = await this.orm.repos.image.findOne({
id: Number(req.params.id),
});
if (!image)
return res
.status(BAD_REQUEST)
.json({
error: "Could not delete image: image doesnt exist in database",
});
this.orm.repos.image.delete({ id: Number(req.params.id) });
const url = new URL(image.url);
unlinkSync(`${config.uploader.upload}${sep}${url.pathname.slice(3)}`);
Logger.get('TypeX.Images.Delete').info(`Image ${image.url} (${image.id}) was deleted from ${config.uploader.upload}${sep}${url.pathname.slice(3)}`)
Logger.get("TypeX.Images.Delete").info(
`Image ${image.url} (${image.id}) was deleted from ${
config.uploader.upload
}${sep}${url.pathname.slice(3)}`
);
return res.status(200).json(image);
} catch (e) {
return res.status(BAD_REQUEST).json({ error: "Could not delete image: " + e.message })
return res
.status(BAD_REQUEST)
.json({ error: "Could not delete image: " + e.message });
}
}
@Get('images/:id')
@Get("images/:id")
@Middleware(cookiesForAPI)
private async imagesUser(req: Request, res: Response) {
const all = await this.orm.repos.image.find({ where: { id: req.params.id }, order: { id: 'ASC' } });
const all = await this.orm.repos.image.find({
where: { id: req.params.id },
order: { id: "ASC" },
});
return res.status(200).json(all);
}
@Get('images/user/pages')
@Get("images/user/pages")
@Middleware(cookiesForAPI)
private async pagedUser(req: Request, res: Response) {
const all = await this.orm.repos.image.find({ where: { user: req.session.user.id }, order: { id: 'ASC' } });
const all = await this.orm.repos.image.find({
where: { user: req.session.user.id },
order: { id: "ASC" },
});
const paged = [];
const pagedNums = [];
while (all.length) paged.push(all.splice(0, 25));
@ -254,59 +487,78 @@ export class APIController {
else return res.status(200).json({ page: paged[Number(req.query.page)] });
}
@Get('shortens')
@Get("shortens")
@Middleware(cookiesForAPI)
private async allShortens(req: Request, res: Response) {
const all = await this.orm.repos.shorten.find({ where: { user: req.session.user.id }, order: { id: 'ASC' } });
const all = await this.orm.repos.shorten.find({
where: { user: req.session.user.id },
order: { id: "ASC" },
});
return res.status(200).json(all);
}
@Get('shortens/:id')
@Get("shortens/:id")
@Middleware(cookiesForAPI)
private async getShorten(req: Request, res: Response) {
const all = await this.orm.repos.shorten.find({ where: { user: req.session.user.id, id: Number(req.params.id) }, order: { id: 'ASC' } });
const all = await this.orm.repos.shorten.find({
where: { user: req.session.user.id, id: Number(req.params.id) },
order: { id: "ASC" },
});
return res.status(200).json(all);
}
@Get('notes')
@Get("notes")
@Middleware(cookiesForAPI)
private async allNotes(req: Request, res: Response) {
const all = await this.orm.repos.note.find({ where: { user: req.session.user.id }, order: { id: 'ASC' } });
const all = await this.orm.repos.note.find({
where: { user: req.session.user.id },
order: { id: "ASC" },
});
return res.status(200).json(all);
}
@Get('notes/:id')
@Get("notes/:id")
@Middleware(cookiesForAPI)
private async getNote(req: Request, res: Response) {
const all = await this.orm.repos.note.find({ where: { user: req.session.user.id, id: Number(req.params.id) }, order: { id: 'ASC' } });
const all = await this.orm.repos.note.find({
where: { user: req.session.user.id, id: Number(req.params.id) },
order: { id: "ASC" },
});
return res.status(200).json(all);
}
@Get('stats')
@Get("stats")
private async getStats(req: Request, res: Response) {
const memory = process.memoryUsage();
const views: number = (await this.orm.repos.image.find()).map((a) => a.views).reduce((a, b) => Number(a) + Number(b), 0);
const clicks: number = (await this.orm.repos.shorten.find()).map((a) => a.clicks).reduce((a, b) => Number(a) + Number(b), 0);
const views: number = (await this.orm.repos.image.find())
.map((a) => a.views)
.reduce((a, b) => Number(a) + Number(b), 0);
const clicks: number = (await this.orm.repos.shorten.find())
.map((a) => a.clicks)
.reduce((a, b) => Number(a) + Number(b), 0);
return res.status(200).json({
memory,
uploadedStatistics: {
views, clicks
views,
clicks,
},
count: {
image: await this.orm.repos.image.count(),
note: await this.orm.repos.note.count(),
shorten: await this.orm.repos.shorten.count(),
user: await this.orm.repos.user.count()
user: await this.orm.repos.user.count(),
},
zipline: {
version: JSON.parse(readFileSync(findFile('package.json', process.cwd()), 'utf8')).version,
database: this.orm.connection.options.type
}
})
version: JSON.parse(
readFileSync(findFile("package.json", process.cwd()), "utf8")
).version,
database: this.orm.connection.options.type,
},
});
}
public set(orm: ORMHandler) {
this.orm = orm;
return this;
}
}
}

View file

@ -1,84 +1,112 @@
import { Controller, Middleware, Get, Post } from '@overnightjs/core';
import { Request, Response } from 'express';
import { ORMHandler } from '..';
import { findFile, checkPassword } from '../util';
import { readFileSync } from 'fs';
import { cookies } from '../middleware/cookies';
import Logger from '@ayanaware/logger';
import { Controller, Middleware, Get, Post } from "@overnightjs/core";
import { Request, Response } from "express";
import { ORMHandler } from "..";
import { findFile, checkPassword } from "../util";
import { readFileSync } from "fs";
import { cookies } from "../middleware/cookies";
import Logger from "@ayanaware/logger";
if (!findFile('config.json', process.cwd())) {
Logger.get('FS').error(`No config.json exists in the ${__dirname}, exiting...`)
if (!findFile("config.json", process.cwd())) {
Logger.get("FS").error(
`No config.json exists in the ${__dirname}, exiting...`
);
process.exit(1);
}
const config = JSON.parse(readFileSync(findFile('config.json', process.cwd()), 'utf8'))
const config = JSON.parse(
readFileSync(findFile("config.json", process.cwd()), "utf8")
);
@Controller('')
@Controller("")
export class IndexController {
public orm: ORMHandler;
@Get('')
@Get("")
@Middleware(cookies)
private async index(req: Request, res: Response) {
const images = await this.orm.repos.image.find({ where: { user: req.session.user.id } });
const users = await this.orm.repos.user.find({ order: { id: 'ASC' } });
const images = await this.orm.repos.image.find({
where: { user: req.session.user.id },
});
const users = await this.orm.repos.user.find({ order: { id: "ASC" } });
const userImages = [];
for (let i = 0; i < users.length; i++) {
userImages[i] = await (await this.orm.repos.image.find({where:{user:users[i].id}})).length
userImages[i] = await (
await this.orm.repos.image.find({ where: { user: users[i].id } })
).length;
}
return res.render('index', { user: req.session.user, images, users, userImages, config })
return res.render("index", {
user: req.session.user,
images,
users,
userImages,
config,
});
}
@Get('login')
@Get("login")
private async login(req: Request, res: Response) {
if (req.session.user || req.cookies.typex_user) return res.redirect('/');
return res.status(200).render('login', { failed: false, config })
if (req.session.user || req.cookies.typex_user) return res.redirect("/");
return res.status(200).render("login", { failed: false, config });
}
@Get('logout')
@Get("logout")
private async logout(req: Request, res: Response) {
Logger.get('TypeX.Auth').info(`User ${req.session.user?.username} (${req.session.user?.id}) logged out`)
Logger.get("TypeX.Auth").info(
`User ${req.session.user?.username} (${req.session.user?.id}) logged out`
);
req.session.user = null;
res.clearCookie('typex_user');
res.redirect('/login');
res.clearCookie("typex_user");
res.redirect("/login");
}
@Post('login')
@Post("login")
private async postLogin(req: Request, res: Response) {
if (req.session.user || req.cookies.typex_user) return res.redirect('/');
if (req.body.username === 'administrator' && req.body.password === config.core.adminPassword) {
if (req.session.user || req.cookies.typex_user) return res.redirect("/");
if (
req.body.username === "administrator" &&
req.body.password === config.core.adminPassword
) {
//@ts-ignore
req.session.user = {
id: 0,
username: 'administrator',
username: "administrator",
password: config.core.adminPassword,
administrator: true
}
res.cookie('typex_user', req.session.user.id, { maxAge: 1036800000 });
Logger.get('TypeX.Auth').info(`Administrator has logged in from IP ${req.headers['x-forwarded-for'] || req.connection.remoteAddress}`)
return res.redirect('/')
administrator: true,
};
res.cookie("typex_user", req.session.user.id, { maxAge: 1036800000 });
Logger.get("TypeX.Auth").info(
`Administrator has logged in from IP ${
req.headers["x-forwarded-for"] || req.connection.remoteAddress
}`
);
return res.redirect("/");
}
const user = await this.orm.repos.user.findOne({ where: { username: req.body.username } });
if (!user) return res.status(200).render('login', { failed: true, config });
if (!checkPassword(req.body.password, user.password)) return res.status(200).render('login', { failed: true, config })
const user = await this.orm.repos.user.findOne({
where: { username: req.body.username },
});
if (!user) return res.status(200).render("login", { failed: true, config });
if (!checkPassword(req.body.password, user.password))
return res.status(200).render("login", { failed: true, config });
req.session.user = user;
res.cookie('typex_user', req.session.user.id, { maxAge: 1036800000 });
return res.redirect('/');
res.cookie("typex_user", req.session.user.id, { maxAge: 1036800000 });
return res.redirect("/");
}
@Get(`${config.shortener.route.slice(1)}/:id`)
private async getShorten(req: Request, res: Response) {
const shorten = await this.orm.repos.shorten.findOne({ key: req.params.id });
if (!shorten) return res.render('404');
const shorten = await this.orm.repos.shorten.findOne({
key: req.params.id,
});
if (!shorten) return res.render("404");
shorten.clicks++;
this.orm.repos.shorten.save(shorten);
return res.redirect(shorten.origin)
return res.redirect(shorten.origin);
}
@Get(`${config.notes.route.slice(1)}/:id`)
private async getNote(req: Request, res: Response) {
const note = await this.orm.repos.note.findOne({ key: req.params.id });
if (!note) return res.render('404');
if (!note) return res.render("404");
return res.send(note.content);
}
@ -86,4 +114,4 @@ export class IndexController {
this.orm = orm;
return this;
}
}
}

View file

@ -1,12 +1,16 @@
import Logger, {ConsoleTransport, DefaultFormatter} from "@ayanaware/logger";
import {ConsoleFormatter} from "../structures/ConsoleFormatter";
import Logger, { ConsoleTransport, DefaultFormatter } from "@ayanaware/logger";
import { ConsoleFormatter } from "../structures/ConsoleFormatter";
Logger.setFormatter(new DefaultFormatter({
disableDefaultColors: true
}));
Logger.setFormatter(
new DefaultFormatter({
disableDefaultColors: true,
})
);
Logger.addTransport(new ConsoleTransport({
formatter: new ConsoleFormatter()
}));
Logger.addTransport(
new ConsoleTransport({
formatter: new ConsoleFormatter(),
})
);
Logger.disableDefaultTransport();
Logger.disableDefaultTransport();

View file

@ -2,22 +2,22 @@ import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class Image {
@PrimaryGeneratedColumn({ type: 'bigint' })
id: number;
@PrimaryGeneratedColumn({ type: "bigint" })
id: number;
@Column("text")
url: string;
@Column("text")
url: string;
@Column("bigint")
user: number;
@Column("bigint")
user: number;
@Column("bigint", { default: 0 })
views: number;
@Column("bigint", { default: 0 })
views: number;
set(options: { url: string, user: number }) {
this.url = options.url;
this.user = options.user;
this.views = 0;
return this;
}
}
set(options: { url: string; user: number }) {
this.url = options.url;
this.user = options.user;
this.views = 0;
return this;
}
}

View file

@ -2,30 +2,35 @@ import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class Note {
@PrimaryGeneratedColumn({ type: 'bigint' })
id: number;
@PrimaryGeneratedColumn({ type: "bigint" })
id: number;
@Column("bigint")
@Column("bigint")
user: number;
@Column("text")
key: string;
@Column("bigint")
creation: number;
@Column("bigint", { nullable: true, default: null })
expriation: number;
@Column("text")
content: string;
set(options: {
user: number;
@Column("text")
key: string;
@Column("bigint")
creation: number;
@Column("bigint", { nullable: true, default: null })
expriation: number;
@Column("text")
content: string;
set(options: { user: number, key: string, content: string, expiration?: number }) {
this.user = options.user;
this.key = options.key;
this.content = options.content;
this.creation = Date.now();
this.expriation = options.expiration ? options.expiration : null;
return this;
}
}
expiration?: number;
}) {
this.user = options.user;
this.key = options.key;
this.content = options.content;
this.creation = Date.now();
this.expriation = options.expiration ? options.expiration : null;
return this;
}
}

View file

@ -2,34 +2,34 @@ import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class Shorten {
@PrimaryGeneratedColumn({ type: 'bigint' })
id: number;
@PrimaryGeneratedColumn({ type: "bigint" })
id: number;
@Column("text")
origin: string;
@Column("text")
origin: string;
@Column("text")
url: string;
@Column("text")
url: string;
@Column("text", { default: "" })
key: string;
@Column("text", { default: "" })
key: string;
@Column("bigint")
user: number;
@Column("bigint")
user: number;
@Column("bigint", { default: 0 })
views: number
@Column("bigint", { default: 0 })
views: number;
@Column("bigint", { default: 0 })
clicks: number;
@Column("bigint", { default: 0 })
clicks: number;
set(options: { key: string, origin: string, url: string, user: number }) {
this.key = options.key;
this.origin = options.origin;
this.url = options.url;
this.user = options.user;
this.views = 0;
this.clicks = 0;
return this;
}
}
set(options: { key: string; origin: string; url: string; user: number }) {
this.key = options.key;
this.origin = options.origin;
this.url = options.url;
this.user = options.user;
this.views = 0;
this.clicks = 0;
return this;
}
}

View file

@ -1,18 +1,22 @@
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import { randomId, findFile } from "../util";
import Logger from "@ayanaware/logger";
import { readFileSync } from 'fs';
import { readFileSync } from "fs";
if (!findFile('config.json', process.cwd())) {
Logger.get('FS').error(`No config.json exists in the ${__dirname}, exiting...`)
if (!findFile("config.json", process.cwd())) {
Logger.get("FS").error(
`No config.json exists in the ${__dirname}, exiting...`
);
process.exit(1);
}
const config = JSON.parse(readFileSync(findFile('config.json', process.cwd()), 'utf8'))
const config = JSON.parse(
readFileSync(findFile("config.json", process.cwd()), "utf8")
);
@Entity()
export class User {
@PrimaryGeneratedColumn({ type: 'bigint' })
@PrimaryGeneratedColumn({ type: "bigint" })
id: number;
@Column("text")
@ -27,11 +31,11 @@ export class User {
@Column("boolean")
administrator: boolean;
set(options: { username: string, password: string, administrator: boolean }) {
set(options: { username: string; password: string; administrator: boolean }) {
this.username = options.username;
this.password = options.password;
this.administrator = options.administrator;
this.token = randomId(config.core.userTokenLength)
this.token = randomId(config.core.userTokenLength);
return this;
}
}

View file

@ -1,31 +1,31 @@
import "./core/Console";
import {
Repository,
Connection,
createConnection
} from "typeorm";
import { Repository, Connection, createConnection } from "typeorm";
import { User } from "./entities/User";
import { ZiplineServer } from "./server";
import Logger from "@ayanaware/logger";
import { Image } from "./entities/Image";
import { findFile } from "./util";
import { readFileSync } from 'fs';
import { readFileSync } from "fs";
import { Shorten } from "./entities/Shorten";
import { Note } from "./entities/Note";
import { notes } from "./interval";
import { GitHub } from "./structures/GitHub";
import { compare, } from 'semver';
import chalk from 'chalk';
import { compare } from "semver";
import chalk from "chalk";
if (!findFile('config.json', process.cwd())) {
Logger.get('FS').error(`No config.json exists in ${__dirname}, exiting...`)
if (!findFile("config.json", process.cwd())) {
Logger.get("FS").error(`No config.json exists in ${__dirname}, exiting...`);
process.exit(1);
}
const config = JSON.parse(readFileSync(findFile('config.json', process.cwd()), 'utf8'))
const config = JSON.parse(
readFileSync(findFile("config.json", process.cwd()), "utf8")
);
if (!config.uploader?.route) {
Logger.get('Zipline.Config').error(`Missing needed property on configuration: upload.route`)
Logger.get("Zipline.Config").error(
`Missing needed property on configuration: upload.route`
);
process.exit(1);
}
@ -41,12 +41,27 @@ export interface ORMHandler {
connection: Connection;
}
const pk = JSON.parse(readFileSync(findFile('package.json', process.cwd()), 'utf8'));
const pk = JSON.parse(
readFileSync(findFile("package.json", process.cwd()), "utf8")
);
(async () => {
if (compare(JSON.parse(await GitHub.getFile('package.json')).version, pk.version) == 1) Logger.get(`Zipline`).info(`Zipline is ${chalk.bold.redBright('outdated')}, you should run ${chalk.bold.whiteBright('./scripts/update.sh')} to get the best features.`);
Logger.get('Zipline').info(`Starting Zipline ${pk.version}`);
if (!config.database) return Logger.get('Config').error("Database is not found in config.")
if (
compare(
JSON.parse(await GitHub.getFile("package.json")).version,
pk.version
) == 1
)
Logger.get(`Zipline`).info(
`Zipline is ${chalk.bold.redBright(
"outdated"
)}, you should run ${chalk.bold.whiteBright(
"./scripts/update.sh"
)} to get the best features.`
);
Logger.get("Zipline").info(`Starting Zipline ${pk.version}`);
if (!config.database)
return Logger.get("Config").error("Database is not found in config.");
const connection = await createConnection(config.database);
const orm: ORMHandler = {
connection,
@ -54,7 +69,7 @@ const pk = JSON.parse(readFileSync(findFile('package.json', process.cwd()), 'utf
user: connection.getRepository(User),
image: connection.getRepository(Image),
shorten: connection.getRepository(Shorten),
note: connection.getRepository(Note)
note: connection.getRepository(Note),
},
};
@ -64,6 +79,6 @@ const pk = JSON.parse(readFileSync(findFile('package.json', process.cwd()), 'utf
);
const server = new ZiplineServer(orm);
server.start();
Logger.get('Interval').info('Starting Notes interval');
const notesInterval = notes(orm);
Logger.get("Interval").info("Starting Notes interval");
notes(orm);
})();

View file

@ -2,16 +2,22 @@ import { ORMHandler } from ".";
import Logger from "@ayanaware/logger";
export function notes(orm: ORMHandler) {
return setInterval(async () => {
const all = await orm.repos.note.find();
for (const note of all) {
if (note.expriation) {
const expiration = Number(note.creation) + Number(note.expriation);
if (Date.now() > expiration) {
orm.repos.note.delete({ id: note.id });
Logger.get('TypeX.Notes').info(`Note deleted ${note.id} and ${note.expriation ? `expired in ${note.expriation}` : `never expired`}`)
}
}
return setInterval(async () => {
const all = await orm.repos.note.find();
for (const note of all) {
if (note.expriation) {
const expiration = Number(note.creation) + Number(note.expriation);
if (Date.now() > expiration) {
orm.repos.note.delete({ id: note.id });
Logger.get("TypeX.Notes").info(
`Note deleted ${note.id} and ${
note.expriation
? `expired in ${note.expriation}`
: `never expired`
}`
);
}
}, 5000)
}
}
}
}, 5000);
}

View file

@ -1,33 +1,41 @@
import Logger from "@ayanaware/logger";
import { Request, Response } from "express";
import { getConnection } from 'typeorm';
import { getConnection } from "typeorm";
import { User } from "../entities/User";
import { findFile } from "../util";
import { readFileSync } from 'fs';
import { readFileSync } from "fs";
if (!findFile('config.json', process.cwd())) {
Logger.get('FS').error(`No config.json exists in ${__dirname}, exiting...`)
if (!findFile("config.json", process.cwd())) {
Logger.get("FS").error(`No config.json exists in ${__dirname}, exiting...`);
process.exit(1);
}
const config = JSON.parse(readFileSync(findFile('config.json', process.cwd()), 'utf8'))
const config = JSON.parse(
readFileSync(findFile("config.json", process.cwd()), "utf8")
);
export async function cookies(req: Request, res: Response, next: any) {
if (req.cookies.typex_user) {
if (typeof req.cookies.typex_user !== 'string') return res.send('Please clear your browser cookies and refresh this page.')
if (Number(req.cookies.typex_user) === 0) {
req.session.user = {
id: 0,
username: 'administrator',
password: config.core.adminPassword,
administrator: true
}
} else req.session.user = await getConnection().getRepository(User).findOne({ id: req.cookies.typex_user });
if (!req.session.user) {
res.clearCookie('typex_user');
req.session.user = null;
return res.redirect('/login')
}
} else return res.redirect('/login');
return next();
if (req.cookies.typex_user) {
if (typeof req.cookies.typex_user !== "string")
return res.send(
"Please clear your browser cookies and refresh this page."
);
if (Number(req.cookies.typex_user) === 0) {
req.session.user = {
id: 0,
username: "administrator",
password: config.core.adminPassword,
administrator: true,
};
} else
req.session.user = await getConnection()
.getRepository(User)
.findOne({ id: req.cookies.typex_user });
if (!req.session.user) {
res.clearCookie("typex_user");
req.session.user = null;
return res.redirect("/login");
}
} else return res.redirect("/login");
return next();
}

View file

@ -1,29 +1,51 @@
import Logger from "@ayanaware/logger";
import { Request, Response } from "express";
import { BAD_REQUEST, INTERNAL_SERVER_ERROR, FORBIDDEN } from 'http-status-codes'
import { getConnection } from 'typeorm';
import {
BAD_REQUEST,
INTERNAL_SERVER_ERROR,
FORBIDDEN,
} from "http-status-codes";
import { getConnection } from "typeorm";
import { User } from "../entities/User";
import { findFile } from "../util";
import { readFileSync } from 'fs';
import { readFileSync } from "fs";
if (!findFile('config.json', process.cwd())) {
Logger.get('FS').error(`No config.json exists in ${__dirname}, exiting...`)
if (!findFile("config.json", process.cwd())) {
Logger.get("FS").error(`No config.json exists in ${__dirname}, exiting...`);
process.exit(1);
}
const config = JSON.parse(readFileSync(findFile('config.json', process.cwd()), 'utf8'))
const config = JSON.parse(
readFileSync(findFile("config.json", process.cwd()), "utf8")
);
export async function cookiesForAPI(req: Request, res: Response, next: any) {
if (req.cookies.typex_user) {
if (typeof req.cookies.typex_user !== 'string') return res.status(BAD_REQUEST).send({ code: BAD_REQUEST, message: "Please clear browser cookies." })
if (Number(req.cookies.typex_user) === 0) req.session.user = {
id: 0,
username: 'administrator',
password: config.core.adminPassword,
administrator: true
}
else req.session.user = await getConnection().getRepository(User).findOne({ id: req.cookies.typex_user });
if (!req.session.user) return res.status(INTERNAL_SERVER_ERROR).send({ code: INTERNAL_SERVER_ERROR, message: "The user that is logged in does not exist" })
} else return res.status(FORBIDDEN).send({ code: FORBIDDEN, message: "Unauthorized" })
return next();
if (req.cookies.typex_user) {
if (typeof req.cookies.typex_user !== "string")
return res
.status(BAD_REQUEST)
.send({ code: BAD_REQUEST, message: "Please clear browser cookies." });
if (Number(req.cookies.typex_user) === 0)
req.session.user = {
id: 0,
username: "administrator",
password: config.core.adminPassword,
administrator: true,
};
else
req.session.user = await getConnection()
.getRepository(User)
.findOne({ id: req.cookies.typex_user });
if (!req.session.user)
return res
.status(INTERNAL_SERVER_ERROR)
.send({
code: INTERNAL_SERVER_ERROR,
message: "The user that is logged in does not exist",
});
} else
return res
.status(FORBIDDEN)
.send({ code: FORBIDDEN, message: "Unauthorized" });
return next();
}

View file

@ -12,22 +12,26 @@ import { APIController } from "./controllers/APIController";
import { IndexController } from "./controllers/IndexController";
import { findFile } from "./util";
if (!findFile('config.json', process.cwd())) {
Logger.get('FS').error(`No config.json exists in the ${__dirname}, exiting...`)
if (!findFile("config.json", process.cwd())) {
Logger.get("FS").error(
`No config.json exists in the ${__dirname}, exiting...`
);
process.exit(1);
}
const config = JSON.parse(fs.readFileSync(findFile('config.json', process.cwd()), 'utf8'))
const config = JSON.parse(
fs.readFileSync(findFile("config.json", process.cwd()), "utf8")
);
export class ZiplineServer extends Server {
constructor(orm: ORMHandler) {
super();
var p = 'loopback';
var p = "loopback";
if (config.core.trustedProxy) {
p += ',' + config.core.trustedProxy;
p += "," + config.core.trustedProxy;
}
this.app.set("trust proxy", p);
this.app.set("view engine", "ejs");
this.app.set("view engine", "ejs");
this.app.use(
session({
secret: config.core.sessionSecret,
@ -37,17 +41,26 @@ export class ZiplineServer extends Server {
);
this.app.use(async (req, res, next) => {
if (!req.url.startsWith(config.uploader.route)) return next();
const upload = await orm.repos.image.findOne({ url: `${config.secure ? "https" : "http"}://${req.headers['host']}${req.url}` });
const upload = await orm.repos.image.findOne({
url: `${config.secure ? "https" : "http"}://${req.headers["host"]}${
req.url
}`,
});
if (!upload) return next();
upload.views++;
orm.repos.image.save(upload);
return next();
})
});
this.app.use(cookies());
try {
this.app.use(config.uploader.route, express.static(config.uploader.upload));
this.app.use(
config.uploader.route,
express.static(config.uploader.upload)
);
} catch (e) {
Logger.get('TypeX.Routes').error(`Could not formulate upload static route`)
Logger.get("TypeX.Routes").error(
`Could not formulate upload static route`
);
process.exit(1);
}
this.app.use("/public", express.static("public"));
@ -57,9 +70,15 @@ export class ZiplineServer extends Server {
if (!config.core.log) return next();
if (req.url.startsWith(config.uploader.route)) return next();
let user = req.session.user;
const users = await orm.repos.user.find({ where: { token: req.headers['authorization'] } });
if (users[0]) user = users[0]
Logger.get('TypeX.Route').info(`Route ${req.url} was accessed by ${user ? user.username : '<no user found>'}`)
const users = await orm.repos.user.find({
where: { token: req.headers["authorization"] },
});
if (users[0]) user = users[0];
Logger.get("TypeX.Route").info(
`Route ${req.url} was accessed by ${
user ? user.username : "<no user found>"
}`
);
return next();
});
@ -70,12 +89,12 @@ export class ZiplineServer extends Server {
const api = new APIController().set(orm);
const index = new IndexController().set(orm);
super.addControllers([index, api]);
this.app.get('*', (req, res) => {
return res.status(200).render('404');
})
this.app.get("*", (req, res) => {
return res.status(200).render("404");
});
this.app.use((err, req, res, next) => {
Logger.get(this.app).error(err);
return res.status(500).render('error');
return res.status(500).render("error");
});
}
@ -85,19 +104,31 @@ export class ZiplineServer extends Server {
try {
const creds = {
key: fs.readFileSync(config.core.ssl.key, "utf-8"),
cert: fs.readFileSync(config.core.ssl.cert, "utf-8")
cert: fs.readFileSync(config.core.ssl.cert, "utf-8"),
};
server = https.createServer(creds, this.app);
} catch (e) {
if (e.code === 'ENOENT') {
Logger.get('ZiplineServer.FS').error(`No file/directory found for ${e.path}`);
if (e.code === "ENOENT") {
Logger.get("ZiplineServer.FS").error(
`No file/directory found for ${e.path}`
);
process.exit(1);
}
}
} else server = http.createServer(this.app);
server.listen(config.core.secure ? config.core.port.secure : config.core.port.unsecure, () => {
Logger.get(ZiplineServer).info('Started server on port ' + String(config.core.secure ? config.core.port.secure : config.core.port.unsecure));
})
server.listen(
config.core.secure ? config.core.port.secure : config.core.port.unsecure,
() => {
Logger.get(ZiplineServer).info(
"Started server on port " +
String(
config.core.secure
? config.core.port.secure
: config.core.port.unsecure
)
);
}
);
}
}

View file

@ -1,5 +1,5 @@
import { Formatter, LogLevel, LogMeta } from "@ayanaware/logger";
import chalk from 'chalk';
import chalk from "chalk";
export class ConsoleFormatter extends Formatter {
public formatError(meta: Readonly<LogMeta>, error: Error): string {
@ -7,31 +7,33 @@ export class ConsoleFormatter extends Formatter {
}
public formatMessage(meta: Readonly<LogMeta>, message: string): string {
return `${this.formatTimestamp()} ${this.formatLevel(meta.level)} ${this.formatName(meta.origin.name)}: ${message}`
return `${this.formatTimestamp()} ${this.formatLevel(
meta.level
)} ${this.formatName(meta.origin.name)}: ${message}`;
}
public formatName(name: string): string {
return `${chalk.greenBright(name)}`
return `${chalk.greenBright(name)}`;
}
public formatTimestamp(): string {
return new Date().toLocaleString().split(', ').join(' ');
return new Date().toLocaleString().split(", ").join(" ");
}
public formatLevel(level: LogLevel): string {
switch (level) {
case LogLevel.DEBUG:
return `${chalk.yellowBright('debug')}`;
return `${chalk.yellowBright("debug")}`;
case LogLevel.ERROR:
return `${chalk.redBright('err')} `;
return `${chalk.redBright("err")} `;
case LogLevel.INFO:
return `${chalk.blue('info')} `;
return `${chalk.blue("info")} `;
case LogLevel.OFF:
return `${chalk.white('off')} `;
return `${chalk.white("off")} `;
case LogLevel.TRACE:
return `${chalk.magenta('trace')} `;
return `${chalk.magenta("trace")} `;
case LogLevel.WARN:
return `${chalk.yellow('warn')} `;
return `${chalk.yellow("warn")} `;
}
}
}
}

View file

@ -1,48 +1,56 @@
import req from 'centra';
import { User } from '../entities/User';
import { Shorten } from '../entities/Shorten';
import { Imaged } from './ImageUtil';
import { Shortened } from './ShortenUtil';
import req from "centra";
import { User } from "../entities/User";
import { Shorten } from "../entities/Shorten";
import { Imaged } from "./ImageUtil";
import { Shortened } from "./ShortenUtil";
export class DiscordWebhook {
public url: string;
constructor(url: string) {
this.url = url;
this.checkExists();
}
async checkExists(): Promise<boolean> {
const json = await (await req(this.url).send()).json();
if (json.code === 10015) throw new Error('Unknown Webhook')
else if (json.code === 50027) throw new Error('Invalid Webhook Token')
else if (json.code) throw new Error(`DiscordAPIError[${json.code}]: ${json.message}`);
return json.code ? false : true;
}
async sendImageUpdate(user: User, image: Imaged, config: any) {
const res = await req(this.url, 'POST')
.header({
'Content-Type': 'application/json'
})
.body({
user: config.discordWebhook.username,
avatar_url: config.discordWebhook.avatarURL,
content: `New image uploaded to <${image.origin}> by ${user.username} (${user.id}). ${image.url})`
})
.send();
public url: string;
constructor(url: string) {
this.url = url;
this.checkExists();
}
async checkExists(): Promise<boolean> {
const json = await (await req(this.url).send()).json();
if (json.code === 10015) throw new Error("Unknown Webhook");
else if (json.code === 50027) throw new Error("Invalid Webhook Token");
else if (json.code)
throw new Error(`DiscordAPIError[${json.code}]: ${json.message}`);
return json.code ? false : true;
}
async sendImageUpdate(user: User, image: Imaged, config: any) {
const res = await req(this.url, "POST")
.header({
"Content-Type": "application/json",
})
.body({
user: config.discordWebhook.username,
avatar_url: config.discordWebhook.avatarURL,
content: `New image uploaded to <${image.origin}> by ${user.username} (${user.id}). ${image.url})`,
})
.send();
if (res.statusCode !== 200) throw new Error(`Couldn't send webhook. (Status: ${res.statusCode})`);
}
async sendShortenUpdate(user: User, shorten: Shorten, ex: Shortened, config: any) {
const res = await req(this.url, 'POST')
.header({
'Content-Type': 'application/json'
})
.body({
user: config.discordWebhook.username,
avatar_url: config.discordWebhook.avatarURL,
content: `New shortened url added to <${ex.origin}> by ${user.username} (${user.id}). <${shorten.origin}> -> <${shorten.url}>`
})
.send();
if (res.statusCode !== 200)
throw new Error(`Couldn't send webhook. (Status: ${res.statusCode})`);
}
async sendShortenUpdate(
user: User,
shorten: Shorten,
ex: Shortened,
config: any
) {
const res = await req(this.url, "POST")
.header({
"Content-Type": "application/json",
})
.body({
user: config.discordWebhook.username,
avatar_url: config.discordWebhook.avatarURL,
content: `New shortened url added to <${ex.origin}> by ${user.username} (${user.id}). <${shorten.origin}> -> <${shorten.url}>`,
})
.send();
if (res.statusCode !== 200) throw new Error(`Couldn't send webhook. (Status: ${res.statusCode})`);
}
}
if (res.statusCode !== 200)
throw new Error(`Couldn't send webhook. (Status: ${res.statusCode})`);
}
}

View file

@ -1,8 +1,10 @@
import req from 'centra';
import req from "centra";
export class GitHub {
public static async getFile(filePath: string) {
const res = await req(`https://raw.githubusercontent.com/ZiplineProject/Zipline/master/${filePath}`).send();
return res.text();
}
}
public static async getFile(filePath: string) {
const res = await req(
`https://raw.githubusercontent.com/ZiplineProject/Zipline/master/${filePath}`
).send();
return res.text();
}
}

View file

@ -1,34 +1,44 @@
import { getType } from 'mime';
import { findFile } from '../util';
import Logger from '@ayanaware/logger';
import { readFileSync } from 'fs';
import { getType } from "mime";
import { findFile } from "../util";
import Logger from "@ayanaware/logger";
import { readFileSync } from "fs";
if (!findFile('config.json', process.cwd())) {
Logger.get('FS').error(`No config.json exists in the ${__dirname}, exiting...`)
process.exit(1);
if (!findFile("config.json", process.cwd())) {
Logger.get("FS").error(
`No config.json exists in the ${__dirname}, exiting...`
);
process.exit(1);
}
const config = JSON.parse(readFileSync(findFile('config.json', process.cwd()), 'utf8'))
const config = JSON.parse(
readFileSync(findFile("config.json", process.cwd()), "utf8")
);
export interface Imaged {
url: string;
origin: string;
protocol: string;
key: string;
extension: string;
mime: string;
url: string;
origin: string;
protocol: string;
key: string;
extension: string;
mime: string;
}
export class ImageUtil {
static parseURL(url: string): Imaged {
const parsed = new URL(url);
return {
url: parsed.href,
origin: parsed.origin,
protocol: parsed.protocol.slice(0, -1),
key: parsed.pathname.startsWith(config.upload.route) ? parsed.pathname.slice(3).split('.')[0] : null,
extension: parsed.pathname.startsWith(config.upload.route) ? parsed.pathname.slice(3).split('.')[1] : null,
mime: parsed.pathname.startsWith(config.upload.route) ? getType(parsed.pathname.slice(3).split('.')[1]) : null
}
}
}
static parseURL(url: string): Imaged {
const parsed = new URL(url);
return {
url: parsed.href,
origin: parsed.origin,
protocol: parsed.protocol.slice(0, -1),
key: parsed.pathname.startsWith(config.upload.route)
? parsed.pathname.slice(3).split(".")[0]
: null,
extension: parsed.pathname.startsWith(config.upload.route)
? parsed.pathname.slice(3).split(".")[1]
: null,
mime: parsed.pathname.startsWith(config.upload.route)
? getType(parsed.pathname.slice(3).split(".")[1])
: null,
};
}
}

View file

@ -1,34 +1,44 @@
import { getType } from 'mime';
import { findFile } from '../util';
import Logger from '@ayanaware/logger';
import { readFileSync } from 'fs';
import { getType } from "mime";
import { findFile } from "../util";
import Logger from "@ayanaware/logger";
import { readFileSync } from "fs";
if (!findFile('config.json', process.cwd())) {
Logger.get('FS').error(`No config.json exists in the ${__dirname}, exiting...`)
process.exit(1);
if (!findFile("config.json", process.cwd())) {
Logger.get("FS").error(
`No config.json exists in the ${__dirname}, exiting...`
);
process.exit(1);
}
const config = JSON.parse(readFileSync(findFile('config.json', process.cwd()), 'utf8'))
const config = JSON.parse(
readFileSync(findFile("config.json", process.cwd()), "utf8")
);
export interface Shortened {
url: string;
origin: string;
protocol: string;
key: string;
extension: string;
mime: string;
url: string;
origin: string;
protocol: string;
key: string;
extension: string;
mime: string;
}
export class ShortenUtil {
static parseURL(url: string): Shortened {
const parsed = new URL(url);
return {
url: parsed.href,
origin: parsed.origin,
protocol: parsed.protocol.slice(0, -1),
key: parsed.pathname.startsWith(config.shorten.route) ? parsed.pathname.slice(3).split('.')[0] : null,
extension: parsed.pathname.startsWith(config.shorten.route) ? parsed.pathname.slice(3).split('.')[1] : null,
mime: parsed.pathname.startsWith(config.shorten.route) ? getType(parsed.pathname.slice(3).split('.')[1]) : null
}
}
}
static parseURL(url: string): Shortened {
const parsed = new URL(url);
return {
url: parsed.href,
origin: parsed.origin,
protocol: parsed.protocol.slice(0, -1),
key: parsed.pathname.startsWith(config.shorten.route)
? parsed.pathname.slice(3).split(".")[0]
: null,
extension: parsed.pathname.startsWith(config.shorten.route)
? parsed.pathname.slice(3).split(".")[1]
: null,
mime: parsed.pathname.startsWith(config.shorten.route)
? getType(parsed.pathname.slice(3).split(".")[1])
: null,
};
}
}

View file

@ -1,33 +1,41 @@
import bcrypt from 'bcrypt';
import { ORMHandler } from '.';
import { User } from './entities/User';
import { Image } from './entities/Image';
import { statSync, readdirSync } from 'fs';
import { join, basename } from 'path';
import { Shorten } from './entities/Shorten';
import bcrypt from "bcrypt";
import { ORMHandler } from ".";
import { User } from "./entities/User";
import { Image } from "./entities/Image";
import { statSync, readdirSync } from "fs";
import { join, basename } from "path";
import { Shorten } from "./entities/Shorten";
export function randomId(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var result = "";
var characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var charactersLength = characters.length;
for (var i = 0; i < length; i++) result += characters.charAt(Math.floor(Math.random() * charactersLength));
for (var i = 0; i < length; i++)
result += characters.charAt(Math.floor(Math.random() * charactersLength));
return result;
}
export function renderTemplate(res, req, template, data = {}) {
const baseData = {
path: req.path,
user: req.isAuthenticated() ? req.user : null,
auth: req.isAuthenticated()
auth: req.isAuthenticated(),
};
res.render(template, Object.assign(baseData, data));
}
export async function getUser(orm: ORMHandler, username: string, password: string, administrator: boolean = false) {
export async function getUser(
orm: ORMHandler,
username: string,
password: string,
administrator: boolean = false
) {
const user = await orm.repos.user.findOne({ username });
if (!user) return orm.repos.user.save(new User().set({ username, password, administrator }));
if (!user)
return orm.repos.user.save(
new User().set({ username, password, administrator })
);
return user;
}
@ -37,9 +45,18 @@ export async function getImage(orm: ORMHandler, url: string, user: number) {
return image;
}
export async function getShorten(orm: ORMHandler, key: string, origin: string, url: string, user: number) {
export async function getShorten(
orm: ORMHandler,
key: string,
origin: string,
url: string,
user: number
) {
const image = await orm.repos.shorten.findOne({ key });
if (!image) return orm.repos.shorten.save(new Shorten().set({ key, origin, url, user }));
if (!image)
return orm.repos.shorten.save(
new Shorten().set({ key, origin, url, user })
);
return image;
}
@ -49,13 +66,17 @@ export function findFile(file, directory) {
const files = readdirSync(dir);
for (const file of files) {
const filepath = join(dir, file);
if (file !== '.git' && file !== 'node_modules' && statSync(filepath).isDirectory()) {
if (
file !== ".git" &&
file !== "node_modules" &&
statSync(filepath).isDirectory()
) {
read(filepath);
} else {
result.push(filepath);
}
}
}(directory));
})(directory);
for (const f of result) {
const base = basename(f);
if (base === file) return f;
@ -63,7 +84,6 @@ export function findFile(file, directory) {
return null;
}
export function checkPassword(pass: string, hash: string): boolean {
return bcrypt.compareSync(pass, hash);
}
@ -71,4 +91,4 @@ export function checkPassword(pass: string, hash: string): boolean {
export function hashPassword(pass: string, saltRounds: number): string {
// console.log(bcrypt.hashSync(pass,saltRounds));
return bcrypt.hashSync(pass, saltRounds);
}
}

View file

@ -1,16 +1,12 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"module": "commonjs",
"target": "esnext",
"resolveJsonModule": true,
"outDir": "./out",
"esModuleInterop": true
},
"exclude": [
"node_modules"
],
"include": [
"src"
]
}
"compilerOptions": {
"experimentalDecorators": true,
"module": "commonjs",
"target": "esnext",
"resolveJsonModule": true,
"outDir": "./out",
"esModuleInterop": true
},
"exclude": ["node_modules"],
"include": ["src"]
}