Compare commits
124 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7354e561e3 | ||
|
|
de7a2e7795 | ||
|
|
c7ff821e79 | ||
|
|
fcddcc0ee7 | ||
|
|
06777cca92 | ||
|
|
7d68dc78eb | ||
|
|
d0615d6fdb | ||
|
|
c1c096c211 | ||
|
|
beb82735fa | ||
|
|
8cff5295e5 | ||
|
|
4d352bc0da | ||
|
|
e25c3dee15 | ||
|
|
9d57b15dfc | ||
|
|
4ab13292ca | ||
|
|
1d43e783f3 | ||
|
|
3cead9dcc0 | ||
|
|
8ae2f85a45 | ||
|
|
0ce137a467 | ||
|
|
10b51a88cb | ||
|
|
8a04f42130 | ||
|
|
62c207fbc3 | ||
|
|
03c0306552 | ||
|
|
4db128deb8 | ||
|
|
ff4d3d81d3 | ||
|
|
254be71007 | ||
|
|
1f682c4976 | ||
|
|
a258474ac0 | ||
|
|
376fa58c10 | ||
|
|
e9fe41e7d2 | ||
|
|
e718506c2f | ||
|
|
b699a0f979 | ||
|
|
e626d24a1f | ||
|
|
3e31fb8acf | ||
|
|
4160c1c5b6 | ||
|
|
061840c297 | ||
|
|
e29acadd41 | ||
|
|
b84907e281 | ||
|
|
1485f2f75e | ||
|
|
294be241af | ||
|
|
91ca59cef5 | ||
|
|
0635545a49 | ||
|
|
18de95ac76 | ||
|
|
39a38cd243 | ||
|
|
6787dc8966 | ||
|
|
2ec4f61805 | ||
|
|
1e0ebb42ff | ||
|
|
e594a21c4d | ||
|
|
fcd396b265 | ||
|
|
54964e307e | ||
|
|
1d4469a7e7 | ||
|
|
1fc264d08b | ||
|
|
0e26c8fb7d | ||
|
|
bda833f713 | ||
|
|
ddb36a1d77 | ||
|
|
51ed15c845 | ||
|
|
b4407616f6 | ||
|
|
1d3087fcad | ||
|
|
66da53caad | ||
|
|
26c8ca79d6 | ||
|
|
d1a6134a95 | ||
|
|
58f8ee63d8 | ||
|
|
cb1e15344d | ||
|
|
4943a6904d | ||
|
|
2d185faef3 | ||
|
|
388076f35b | ||
|
|
15d86bf097 | ||
|
|
6d0db59e5e | ||
|
|
2fe01b860e | ||
|
|
231a17471b | ||
|
|
d45953a7f8 | ||
|
|
3fa5b1e0a2 | ||
|
|
0c7c0ef27e | ||
|
|
7b288f1503 | ||
|
|
91b2b9b2de | ||
|
|
4fe9370ce2 | ||
|
|
db02b1ddb3 | ||
|
|
73267e3e90 | ||
|
|
93edf6ec19 | ||
|
|
6f2b0bbd0d | ||
|
|
79b5110e21 | ||
|
|
cf5df1150d | ||
|
|
f4793e7f30 | ||
|
|
76acc54e84 | ||
|
|
044c9adc0a | ||
|
|
5fb9d80cfd | ||
|
|
930056b323 | ||
|
|
9acc2d8768 | ||
|
|
1f32cadff0 | ||
|
|
85d4cd6ae1 | ||
|
|
1ea6ffffec | ||
|
|
132c55397e | ||
|
|
c9fa30cd65 | ||
|
|
3b4e3da1cc | ||
|
|
90df30de59 | ||
|
|
171b7111bb | ||
|
|
20dbb14903 | ||
|
|
07aad78993 | ||
|
|
0e03736923 | ||
|
|
cb8bde63f0 | ||
|
|
5494f52ddf | ||
|
|
29a94ec843 | ||
|
|
c37e933cc9 | ||
|
|
01bf445644 | ||
|
|
20c3bf9910 | ||
|
|
5c2ee55994 | ||
|
|
8790d9b2c0 | ||
|
|
46fa7a84e3 | ||
|
|
bacb4d5d8d | ||
|
|
28e9d1194e | ||
|
|
811ee2a87d | ||
|
|
281580b879 | ||
|
|
aaeac1b7e3 | ||
|
|
f9d17e9765 | ||
|
|
409207ab7d | ||
|
|
ba33756416 | ||
|
|
a6cf2a5c46 | ||
|
|
9d2eb09789 | ||
|
|
346ed4d17a | ||
|
|
d66d50ef73 | ||
|
|
4be174e325 | ||
|
|
9023978d0f | ||
|
|
8ddd678864 | ||
|
|
1c18828e1b | ||
|
|
c2553c853f |
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Executable file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ""
|
||||
labels: bug
|
||||
assignees: dicedtomatoreal
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
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 '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
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]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
27
.github/workflows/node.js.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: Node.js CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
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
|
||||
5
.gitignore
vendored
Normal file → Executable file
@@ -2,8 +2,9 @@ node_modules
|
||||
temp
|
||||
tmp
|
||||
uploads
|
||||
config.json
|
||||
config*.json
|
||||
out
|
||||
public/assets/pd_logo.png
|
||||
.idea
|
||||
.vscode
|
||||
ssl/localhost.key
|
||||
ssl/localhost.crt
|
||||
4
.prettierignore
Normal file
@@ -0,0 +1,4 @@
|
||||
dist
|
||||
scripts
|
||||
public
|
||||
views
|
||||
1
.prettierrc.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
76
CODE_OF_CONDUCT.md
Executable file
@@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at . All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
21
LICENSE
Executable file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 dicedtomato
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
328
OLDREADME.md
Executable file
@@ -0,0 +1,328 @@
|
||||
# TypeX
|
||||
|
||||
A TypeScript based Image/File uploading server. Fast and Elegant.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Prerequisites](#prerequisites)
|
||||
1. [Node](#node--typescript)
|
||||
2. [Common Databases](#common-databases)
|
||||
3. [Installing Database Drivers](#installing-database-drivers)
|
||||
1. [PostgreSQL](#getting-postgresql-drivers)
|
||||
2. [CockroachDB](#getting-cockroachdb-drivers)
|
||||
3. [MySQL](#getting-mysql-drivers)
|
||||
4. [MariaDB](#getting-mariadb-drivers)
|
||||
5. [Microsoft SQL Server](#getting-microsoft-sql-drivers)
|
||||
2. [Updating TypeX](#updating-typex)
|
||||
3. [Installation](#installation)
|
||||
1. [Get the Source](#get-the-source--install-dependencies)
|
||||
2. [Setting up configurations](#configuration-options)
|
||||
1. [Upload Size](#upload)
|
||||
2. [User Settings](#user-settings)
|
||||
3. [Site Settings](#site-settings)
|
||||
4. [SSL Settings](#site-ssl-settings)
|
||||
5. [Administrator user](#administrator-user)
|
||||
6. [Database configuration](#database-configuration)
|
||||
7. [Session Secret](#session-secret)
|
||||
8. [Particles.JS](#meta-configuration)
|
||||
3. [Example Config](#example-config)
|
||||
4. [Compiling Source](#compiling-typescript-for-running)
|
||||
5. [Running Compiled Source](#running-compiled-source)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Dependencies needed for running TypeX
|
||||
|
||||
### Node & Typescript
|
||||
|
||||
Node.JS is what runs the show, and you will need it before anything else. Install it [here](https://nodejs.org)
|
||||
|
||||
Once Node is installed install Typescript by doing
|
||||
|
||||
```sh
|
||||
npm i typescript -g
|
||||
```
|
||||
|
||||
Verify your installation by running these commands
|
||||
|
||||
```sh
|
||||
tsc -v
|
||||
npm -v
|
||||
node -v
|
||||
```
|
||||
|
||||
They should all output something along the lines of
|
||||
|
||||
```sh
|
||||
-> tsc -v
|
||||
Version 3.8.3
|
||||
-> npm -v
|
||||
node 6.14.4
|
||||
-> node -v
|
||||
v13.13.0
|
||||
```
|
||||
|
||||
### Common Databases
|
||||
|
||||
- [PostgreSQL](https://www.postgresql.org/ "PostgresSQL")
|
||||
- [CockroachDB](https://www.cockroachlabs.com/ "Cockroach Labs")
|
||||
- [MySQL](https://www.mysql.com/ "MySQL")
|
||||
- [MariaDB](https://www.mariadb.com/ "MariaDB")
|
||||
- [Microsoft SQL Server](https://www.microsoft.com/en-us/sql-server/ "Microsoft SQL Server")
|
||||
- [MongoDB](https://www.mongodb.com/ "MongoDB") (Coming soon!)
|
||||
|
||||
(check out [this](https://github.com/typeorm/typeorm/blob/master/docs/connection-options.md) for all types, you will need to use a different ORM config later on, view [this](https://github.com/typeorm/typeorm/blob/master/docs/connection-options.md#common-connection-options) for every option, more on this on Database configuration setup step)
|
||||
|
||||
### Installing Database Drivers
|
||||
|
||||
In this installation step, you will be installing the drivers of your choice database.
|
||||
|
||||
#### Getting PostgreSQL Drivers
|
||||
|
||||
Run the following command in order to get PostgreSQL drivers
|
||||
|
||||
```sh
|
||||
npm i pg --save-dev
|
||||
```
|
||||
|
||||
#### Getting CockroachDB Drivers
|
||||
|
||||
Run the following command in order to get CockroachDB drivers
|
||||
|
||||
```sh
|
||||
npm i cockroachdb --save-dev
|
||||
```
|
||||
|
||||
#### Getting MySQL Drivers
|
||||
|
||||
Run the following command in order to get MySQL drivers
|
||||
|
||||
```sh
|
||||
npm i mysql --save-dev
|
||||
```
|
||||
|
||||
#### Getting MariaDB Drivers
|
||||
|
||||
Run the following command in order to get MariaDB drivers
|
||||
|
||||
```sh
|
||||
npm i mariadb --save-dev
|
||||
```
|
||||
|
||||
#### Getting Microsoft SQL Drivers
|
||||
|
||||
Run the following command in order to get Microsoft SQL drivers
|
||||
|
||||
```sh
|
||||
npm i mssql --save-dev
|
||||
```
|
||||
|
||||
## Updating TypeX
|
||||
|
||||
Updating TypeX, is very simple. You can use this one-liner to update and compile code.
|
||||
|
||||
1. Run the following in the `typex` directory, if there were config changes, you should change them before this command.
|
||||
|
||||
```sh
|
||||
git pull && tsc -p .
|
||||
```
|
||||
|
||||
2. After that, you just need to restart the process for changes to take effect.
|
||||
|
||||
## Installation
|
||||
|
||||
Now that you have considered what prerequisites you would like, lets actually install this! This installation is based on Linux systems, yet will work on both MacOSX and Windows with their respective commands
|
||||
|
||||
### Get the Source & Install Dependencies
|
||||
|
||||
```sh
|
||||
git clone https://github.com/dicedtomatoreal/typex
|
||||
cd typex
|
||||
tsc -p .
|
||||
npm start
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
|
||||
Every single configuration option will be listed here
|
||||
|
||||
#### Upload
|
||||
|
||||
**Config Property:** `upload`
|
||||
|
||||
| Config Property | Type | Description / Expected Values |
|
||||
| ------------------- | ------- | ------------------------------------------------------------ |
|
||||
| `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` |
|
||||
|
||||
#### User Settings
|
||||
|
||||
**Config Property:** `user`
|
||||
|
||||
| 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 |
|
||||
|
||||
#### Site SSL Settings
|
||||
|
||||
**Config Property:** `site.ssl`
|
||||
|
||||
| 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` |
|
||||
|
||||
#### 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) |
|
||||
|
||||
#### Database Configuration
|
||||
|
||||
**Config Property:** `orm`
|
||||
|
||||
| Config Property | Type | Description / Expected Values |
|
||||
| ----------------- | -------- | --------------------------------------------------------------------------------- |
|
||||
| `orm.type` | string | `mariadb`, `mysql`, `postgres`, `cockroach`, `mssql` |
|
||||
| `orm.host` | string | `localhost` or different IP |
|
||||
| `orm.port` | integer | `5432` or different pot |
|
||||
| `orm.username` | string | username |
|
||||
| `orm.password` | string | password |
|
||||
| `orm.database` | string | database to use |
|
||||
| `orm.synchronize` | boolean | synchronize database to database, or not |
|
||||
| `orm.logging` | boolean | log all queries |
|
||||
| `orm.entities` | string[] | entity paths (should not be edited, and should be `["out/src/entities/**/*.js"]`) |
|
||||
|
||||
#### Session Secret
|
||||
|
||||
**Config Property:** `sessionSecret`
|
||||
|
||||
A Random string of characters (anything)
|
||||
|
||||
#### Session Secret
|
||||
|
||||
**Config Property:** `saltRounds`
|
||||
|
||||
The ammount of salt rounds needed to salt a password! (used for password encryption)
|
||||
|
||||
#### Meta Configuration
|
||||
|
||||
**Config Property:** `meta`
|
||||
|
||||
Particles.JS, can be enabled and it's config can be changed willingly.
|
||||
|
||||
| Config Property | Type | Description / Expected Values |
|
||||
| --------------- | ------ | -------------------------------------------------------------------------------------------- |
|
||||
| `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"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Compiling Typescript for running
|
||||
|
||||
Compile the Typescript code before running the code, or you can run it with `ts-node` which is not recommended. **_MAKE SURE YOU ARE IN THE PROJECT DIR!_**
|
||||
|
||||
```sh
|
||||
tsc -p .
|
||||
```
|
||||
|
||||
### Running Compiled Source
|
||||
|
||||
Run the webserver by running
|
||||
|
||||
```sh
|
||||
node out/src
|
||||
```
|
||||
|
||||
## How you can upload
|
||||
|
||||
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>"}` |
|
||||
400
README.md
Normal file → Executable file
@@ -1,387 +1,31 @@
|
||||
# TypeX
|
||||
# Stop using Zipline 1!
|
||||
This current branch is discontinued, and will not be getting any updates. So...
|
||||
Switch to [Zipline-Next](https://github.com/ziplineproject/zipline/tree/next)!<br>
|
||||
Switch to [Zipline-Next](https://github.com/ziplineproject/zipline/tree/next)!<br>
|
||||
Switch to [Zipline-Next](https://github.com/ziplineproject/zipline/tree/next)!<br>
|
||||
Switch to [Zipline-Next](https://github.com/ziplineproject/zipline/tree/next)!<br>
|
||||
# Zipline
|
||||
|
||||
A TypeScript based Image/File uploading server. Fast and Elegant.
|
||||
Zipline is a Typescript based image/file uploading service & URL shortening service! Simple, and elegant.
|
||||
|
||||
## Table of Contents
|
||||
# Bleeding Edge
|
||||
|
||||
1. Prerequisites
|
||||
1. Node
|
||||
2. Common Databases
|
||||
3. Installing Database Drivers
|
||||
1. PostgreSQL
|
||||
2. CockroachDB
|
||||
3. MySQL
|
||||
4. MariaDB
|
||||
5. Microsoft SQL Server
|
||||
2. Installation
|
||||
1. Get the Source
|
||||
2. Setting up configurations
|
||||
1. Upload Size
|
||||
2. Site Settings
|
||||
3. SSL Settings
|
||||
4. Administrator user
|
||||
5. Database configuration
|
||||
6. Session Secret
|
||||
7. Particles.JS
|
||||
3. Example Config
|
||||
4. Compiling Source
|
||||
5. Running Compiled Source
|
||||
Want to try the bleeding edge version of Zipline? Fear no more, [click here to view the branch (Zipline-Next)](https://github.com/ziplineproject/zipline/tree/next)! As it is still a work in progress piece, no documentation is written for it, and there is very little functionality within it, so try at your own risk! If you would like help to set it up join our [Discord](https://discord.gg/PWU8rxy).
|
||||
|
||||
## Prerequisites
|
||||
# Images
|
||||
|
||||
Dependencies needed for running TypeX
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
### Node & Typescript
|
||||
# Bugs?
|
||||
|
||||
Node.JS is what runs the show, and you will need it before anything else. Install it [here](https://nodejs.org)
|
||||
Make sure to open an issue if you think you ran into an issue, or need help with anything.
|
||||
|
||||
Once Node is installed install Typescript by doing
|
||||
# Where is all the old information here?
|
||||
|
||||
```sh
|
||||
npm i typescript -g
|
||||
```
|
||||
|
||||
Verify your installation by running these commands
|
||||
|
||||
```sh
|
||||
tsc -v
|
||||
npm -v
|
||||
node -v
|
||||
```
|
||||
|
||||
They should all output something along the lines of
|
||||
|
||||
```sh
|
||||
-> tsc -v
|
||||
Version 3.8.3
|
||||
-> npm -v
|
||||
node 6.14.4
|
||||
-> node -v
|
||||
v13.13.0
|
||||
```
|
||||
|
||||
### Common Databases
|
||||
|
||||
- PostgreSQL
|
||||
- CockroachDB
|
||||
- MySQL
|
||||
- MariaDB
|
||||
- Microsoft SQL Server
|
||||
- MongoDB (Coming soon!)
|
||||
|
||||
(check out [this](https://github.com/typeorm/typeorm/blob/master/docs/connection-options.md) for all types, you will need to use a different ORM config later on, view [this](https://github.com/typeorm/typeorm/blob/master/docs/connection-options.md#common-connection-options) for every option, more on this on Database configuration setup step)
|
||||
|
||||
### Installing Database Drivers
|
||||
|
||||
In this installation step, you will be installing the drivers of your choice database.
|
||||
|
||||
#### Getting PostgreSQL Drivers
|
||||
|
||||
Run the following command in order to get PostgreSQL drivers
|
||||
|
||||
```sh
|
||||
npm i pg --save-dev
|
||||
```
|
||||
|
||||
#### Getting CockroachDB Drivers
|
||||
|
||||
Run the following command in order to get CockroachDB drivers
|
||||
|
||||
```sh
|
||||
npm i cockroachdb --save-dev
|
||||
```
|
||||
|
||||
#### Getting MySQL Drivers
|
||||
|
||||
Run the following command in order to get MySQL drivers
|
||||
|
||||
```sh
|
||||
npm i mysql --save-dev
|
||||
```
|
||||
|
||||
#### Getting MariaDB Drivers
|
||||
|
||||
Run the following command in order to get MariaDB drivers
|
||||
|
||||
```sh
|
||||
npm i mariadb --save-dev
|
||||
```
|
||||
|
||||
#### Getting Microsoft SQL Drivers
|
||||
|
||||
Run the following command in order to get Microsoft SQL drivers
|
||||
|
||||
```sh
|
||||
npm i mssql --save-dev
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
Now that you have considered what prerequisites you would like, lets actually install this! This installation is based on Linux systems, yet will work on both MacOSX and Windows with their respective commands
|
||||
|
||||
### Get the Source & Install Dependencies
|
||||
|
||||
You can get the source from the releases
|
||||
|
||||
```sh
|
||||
wget <RELEASE TAR BALL>
|
||||
tar -xvf <REALASE>
|
||||
cd <REALASE>
|
||||
npm i
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
|
||||
Every single configuration option will be listed here
|
||||
|
||||
#### Upload
|
||||
|
||||
**Config Property:** `upload`
|
||||
|
||||
| Config Property | Type | Description / Expected Values |
|
||||
| ------------------- | ------- | ------------------------------------------------------------ |
|
||||
| `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) |
|
||||
|
||||
#### 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](#ssl-settings)) |
|
||||
|
||||
#### SSL Settings
|
||||
|
||||
**Config Property:** `site.ssl`
|
||||
|
||||
| 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` |
|
||||
|
||||
#### 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) |
|
||||
| `administrator.authorization` | string | authorization token that could be used for uploading (NOT RECOMENDED, set this to a SECURE master token) |
|
||||
|
||||
#### Database Configuration
|
||||
|
||||
**Config Property:** `orm`
|
||||
|
||||
| Config Property | Type | Description / Expected Values |
|
||||
| ----------------- | -------- | --------------------------------------------------------------------------------- |
|
||||
| `orm.type` | string | `mariadb`, `mysql`, `postgres`, `cockroach`, `mssql` |
|
||||
| `orm.host` | string | `localhost` or different IP |
|
||||
| `orm.port` | integer | `5432` or different pot |
|
||||
| `orm.username` | string | username |
|
||||
| `orm.password` | string | password |
|
||||
| `orm.database` | string | database to use |
|
||||
| `orm.synchronize` | boolean | synchronize database to database, or not |
|
||||
| `orm.logging` | boolean | log all queries |
|
||||
| `orm.entities` | string[] | entity paths (should not be edited, and should be `["out/src/entities/**/*.js"]`) |
|
||||
|
||||
#### Session Secret
|
||||
|
||||
**Config Property:** `sessionSecret`
|
||||
|
||||
A Random string of characters (anything)
|
||||
|
||||
#### Meta Configuration
|
||||
|
||||
**Config Property:** `meta`
|
||||
|
||||
Particles.JS, can be enabled and it's config can be changed willingly.
|
||||
|
||||
| Config Property | Type | Description / Expected Values |
|
||||
| --------------- | ------ | -------------------------------------------------------------------------------------------- |
|
||||
| `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` |
|
||||
|
||||
#### Particles.JS Configuration
|
||||
|
||||
**Config Property:** `particles`
|
||||
|
||||
Particles.JS, can be enabled and it's config can be changed willingly.
|
||||
|
||||
| Config Property | Type | Description / Expected Values |
|
||||
| ------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `particles.enabled` | boolean | Whether particles should show up on routes |
|
||||
| `particles.config` | particles config | Config from [this](https://vincentgarreau.com/particles.js/) play around with the configuration, then paste the JSON to here |
|
||||
|
||||
### Example Config
|
||||
|
||||
```json
|
||||
{
|
||||
"upload": {
|
||||
"fileLength": 6,
|
||||
"tempDir": "./temp",
|
||||
"uploadDir": "./uploads"
|
||||
},
|
||||
"meta": {
|
||||
"favicon": "/public/assets/typex_small.png",
|
||||
"title": "TypeX"
|
||||
},
|
||||
"site": {
|
||||
"protocol": "https",
|
||||
"ssl": {
|
||||
"key": "./ssl/server.key",
|
||||
"cert": "./ssl/server.crt"
|
||||
},
|
||||
"serveHTTPS": 443, // https port to serve on (will be used if protocol is https)
|
||||
"serveHTTP": 8000 // http port to serve on (will be used if protocol is http)
|
||||
},
|
||||
"administrator": {
|
||||
"password": "1234",
|
||||
"authorization": "Administrator 1234"
|
||||
},
|
||||
"orm": {
|
||||
"type": "postgres",
|
||||
"host": "localhost",
|
||||
"port": 5432,
|
||||
"username": "user",
|
||||
"password": "1234",
|
||||
"database": "typex",
|
||||
"synchronize": true,
|
||||
"logging": false,
|
||||
"entities": ["out/src/entities/**/*.js"]
|
||||
},
|
||||
"sessionSecret": "qwertyuiopasdfghjklzxcvbnm",
|
||||
"particles": {
|
||||
"enabled": true,
|
||||
"settings": {
|
||||
"particles": {
|
||||
"number": {
|
||||
"value": 52,
|
||||
"density": {
|
||||
"enable": true,
|
||||
"value_area": 800
|
||||
}
|
||||
},
|
||||
"color": {
|
||||
"value": "#cd4c4c"
|
||||
},
|
||||
"shape": {
|
||||
"type": "circle",
|
||||
"stroke": {
|
||||
"width": 0,
|
||||
"color": "#000000"
|
||||
},
|
||||
"polygon": {
|
||||
"nb_sides": 9
|
||||
},
|
||||
"image": {
|
||||
"src": "img/github.svg",
|
||||
"width": 60,
|
||||
"height": 100
|
||||
}
|
||||
},
|
||||
"opacity": {
|
||||
"value": 0.5,
|
||||
"random": false,
|
||||
"anim": {
|
||||
"enable": false,
|
||||
"speed": 1,
|
||||
"opacity_min": 0.1,
|
||||
"sync": false
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"value": 0,
|
||||
"random": true,
|
||||
"anim": {
|
||||
"enable": false,
|
||||
"speed": 40,
|
||||
"size_min": 0.1,
|
||||
"sync": false
|
||||
}
|
||||
},
|
||||
"line_linked": {
|
||||
"enable": true,
|
||||
"distance": 150,
|
||||
"color": "#ffffff",
|
||||
"opacity": 0.4,
|
||||
"width": 1
|
||||
},
|
||||
"move": {
|
||||
"enable": true,
|
||||
"speed": 6,
|
||||
"direction": "none",
|
||||
"random": false,
|
||||
"straight": false,
|
||||
"out_mode": "out",
|
||||
"bounce": false,
|
||||
"attract": {
|
||||
"enable": false,
|
||||
"rotateX": 600,
|
||||
"rotateY": 1200
|
||||
}
|
||||
}
|
||||
},
|
||||
"interactivity": {
|
||||
"detect_on": "canvas",
|
||||
"events": {
|
||||
"onhover": {
|
||||
"enable": false,
|
||||
"mode": "grab"
|
||||
},
|
||||
"onclick": {
|
||||
"enable": false,
|
||||
"mode": "repulse"
|
||||
},
|
||||
"resize": true
|
||||
},
|
||||
"modes": {
|
||||
"grab": {
|
||||
"distance": 400,
|
||||
"line_linked": {
|
||||
"opacity": 1
|
||||
}
|
||||
},
|
||||
"bubble": {
|
||||
"distance": 400,
|
||||
"size": 40,
|
||||
"duration": 2,
|
||||
"opacity": 8,
|
||||
"speed": 3
|
||||
},
|
||||
"repulse": {
|
||||
"distance": 200,
|
||||
"duration": 0.4
|
||||
},
|
||||
"push": {
|
||||
"particles_nb": 4
|
||||
},
|
||||
"remove": {
|
||||
"particles_nb": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"retina_detect": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Compiling Typescript for running
|
||||
|
||||
Compile the Typescript code before running the code, or you can run it with `ts-node` which is not recommended. **_MAKE SURE YOU ARE IN THE PROJECT DIR!_**
|
||||
|
||||
```sh
|
||||
tsc -p .
|
||||
```
|
||||
|
||||
### Running Compiled Source
|
||||
|
||||
Run the webserver by running
|
||||
|
||||
```sh
|
||||
node out/src
|
||||
```
|
||||
All configuration options have been moved to the wiki, as the README was getting too long and hard to manage.
|
||||
|
||||
582
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "typex",
|
||||
"version": "0.1.0",
|
||||
"version": "2.1.4",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -12,16 +12,6 @@
|
||||
"fecha": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"@forevolve/bootstrap-dark": {
|
||||
"version": "1.0.0-alpha.981",
|
||||
"resolved": "https://registry.npmjs.org/@forevolve/bootstrap-dark/-/bootstrap-dark-1.0.0-alpha.981.tgz",
|
||||
"integrity": "sha512-3oN8O8nrbfziK4d0QJ3xcL7zctaEWiNaPGX90xsuXxCoW9kVwRbb1Mz97W/yf/8vklq3vycqz9ieCAUY92P89A==",
|
||||
"requires": {
|
||||
"bootstrap": "^4.4.1",
|
||||
"jquery": "^3.4.1",
|
||||
"popper.js": "^1.16.1"
|
||||
}
|
||||
},
|
||||
"@overnightjs/core": {
|
||||
"version": "1.6.15",
|
||||
"resolved": "https://registry.npmjs.org/@overnightjs/core/-/core-1.6.15.tgz",
|
||||
@@ -32,6 +22,11 @@
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@types/bcrypt": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-3.0.0.tgz",
|
||||
"integrity": "sha512-nohgNyv+1ViVcubKBh0+XiNJ3dO8nYu///9aJ4cgSqv70gBL+94SNy/iC2NLzKPT2Zt/QavrOkBVbZRLZmw6NQ=="
|
||||
},
|
||||
"@types/body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz",
|
||||
@@ -41,6 +36,14 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/centra": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/centra/-/centra-2.2.0.tgz",
|
||||
"integrity": "sha512-TUpM1QoIgXa2VL1LnWbTgde39+rRnsOtvb0v8ZrX8t51g8K1Wwu4HAEXcBg5a56GV5Vm/KazyE5+g4AW8YbKNg==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/color-name": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
@@ -119,6 +122,14 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
|
||||
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA=="
|
||||
},
|
||||
"@types/semver": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.1.tgz",
|
||||
"integrity": "sha512-ooD/FJ8EuwlDKOI6D9HWxgIgJjMg2cuziXm/42npDC8y4NjxplBUn9loewZiBNCt44450lHAU0OSb51/UqXeag==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/serve-static": {
|
||||
"version": "1.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz",
|
||||
@@ -128,6 +139,11 @@
|
||||
"@types/mime": "*"
|
||||
}
|
||||
},
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
||||
},
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
@@ -165,6 +181,49 @@
|
||||
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
|
||||
"integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY="
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
|
||||
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
|
||||
},
|
||||
"are-we-there-yet": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
|
||||
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
|
||||
"requires": {
|
||||
"delegates": "^1.0.0",
|
||||
"readable-stream": "^2.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
@@ -188,6 +247,57 @@
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
|
||||
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
|
||||
},
|
||||
"bcrypt": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.0.tgz",
|
||||
"integrity": "sha512-jB0yCBl4W/kVHM2whjfyqnxTmOHkCX4kHEa5nYKSoGeYe8YrjTYTc87/6bwt1g8cmV0QrbhKriETg9jWtcREhg==",
|
||||
"requires": {
|
||||
"node-addon-api": "^3.0.0",
|
||||
"node-pre-gyp": "0.15.0"
|
||||
}
|
||||
},
|
||||
"bl": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz",
|
||||
"integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"readable-stream": "^2.3.5",
|
||||
"safe-buffer": "^5.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
|
||||
"dev": true
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||
@@ -205,11 +315,6 @@
|
||||
"type-is": "~1.6.17"
|
||||
}
|
||||
},
|
||||
"bootstrap": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.4.1.tgz",
|
||||
"integrity": "sha512-tbx5cHubwE6e2ZG7nqM3g/FZ5PQEDMWmMGNrCUBVRPHXTJaH7CBDdsLeu3eCh3B1tzAxTnAbtmrzvWEvT2NNEA=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@@ -219,6 +324,12 @@
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"bson": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.4.tgz",
|
||||
"integrity": "sha512-S/yKGU1syOMzO86+dGpg2qGoDL0zvzcb262G+gqEy6TgP6rt6z6qxSFX/8X6vLC91P7G7C3nLs0+bvDzmvBA3Q==",
|
||||
"dev": true
|
||||
},
|
||||
"buffer": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
|
||||
@@ -258,6 +369,11 @@
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
|
||||
},
|
||||
"centra": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/centra/-/centra-2.4.2.tgz",
|
||||
"integrity": "sha512-f1RaP0V1HqVNEXfLfjNBthB2yy3KnSGnPCnOPCFLUk9e/Z4rNJ8nBaJNnghflnp88mi1IT8mfmW+HlMS1/H+bg=="
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
@@ -268,6 +384,11 @@
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
||||
},
|
||||
"cli-highlight": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.4.tgz",
|
||||
@@ -355,6 +476,11 @@
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
|
||||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
@@ -413,6 +539,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
|
||||
},
|
||||
"content-disposition": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
|
||||
@@ -463,6 +594,22 @@
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
|
||||
},
|
||||
"deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
|
||||
},
|
||||
"delegates": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
|
||||
},
|
||||
"denque": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
|
||||
"integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==",
|
||||
"dev": true
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
@@ -473,6 +620,11 @@
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
},
|
||||
"detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
|
||||
},
|
||||
"dicer": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
|
||||
@@ -634,11 +786,67 @@
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
|
||||
"integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
|
||||
"requires": {
|
||||
"minipass": "^2.6.0"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"gauge": {
|
||||
"version": "2.7.4",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
||||
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
|
||||
"requires": {
|
||||
"aproba": "^1.0.3",
|
||||
"console-control-strings": "^1.0.0",
|
||||
"has-unicode": "^2.0.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"signal-exit": "^3.0.0",
|
||||
"string-width": "^1.0.1",
|
||||
"strip-ansi": "^3.0.1",
|
||||
"wide-align": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
||||
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
"strip-ansi": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
@@ -677,6 +885,11 @@
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
||||
},
|
||||
"has-unicode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
|
||||
},
|
||||
"highlight.js": {
|
||||
"version": "9.18.1",
|
||||
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.1.tgz",
|
||||
@@ -712,6 +925,14 @@
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
|
||||
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
|
||||
},
|
||||
"ignore-walk": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
|
||||
"integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
|
||||
"requires": {
|
||||
"minimatch": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
@@ -726,6 +947,11 @@
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
|
||||
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
@@ -741,11 +967,6 @@
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
||||
},
|
||||
"jquery": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.0.tgz",
|
||||
"integrity": "sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ=="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.13.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
|
||||
@@ -768,6 +989,13 @@
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||
},
|
||||
"memory-pager": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
|
||||
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
@@ -809,6 +1037,23 @@
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
|
||||
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"minizlib": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
|
||||
"integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
|
||||
"requires": {
|
||||
"minipass": "^2.9.0"
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
@@ -817,6 +1062,20 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"mongodb": {
|
||||
"version": "3.5.8",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.8.tgz",
|
||||
"integrity": "sha512-jz7mR58z66JKL8Px4ZY+FXbgB7d0a0hEGCT7kw8iye46/gsqPrOEpZOswwJ2BQlfzsrCLKdsF9UcaUfGVN2HrQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bl": "^2.2.0",
|
||||
"bson": "^1.1.4",
|
||||
"denque": "^1.4.1",
|
||||
"require_optional": "^1.0.1",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"saslprep": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
@@ -847,11 +1106,113 @@
|
||||
"thenify-all": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"needle": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz",
|
||||
"integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==",
|
||||
"requires": {
|
||||
"debug": "^3.2.6",
|
||||
"iconv-lite": "^0.4.4",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||
},
|
||||
"node-addon-api": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.0.tgz",
|
||||
"integrity": "sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg=="
|
||||
},
|
||||
"node-pre-gyp": {
|
||||
"version": "0.15.0",
|
||||
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz",
|
||||
"integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==",
|
||||
"requires": {
|
||||
"detect-libc": "^1.0.2",
|
||||
"mkdirp": "^0.5.3",
|
||||
"needle": "^2.5.0",
|
||||
"nopt": "^4.0.1",
|
||||
"npm-packlist": "^1.1.6",
|
||||
"npmlog": "^4.0.2",
|
||||
"rc": "^1.2.7",
|
||||
"rimraf": "^2.6.1",
|
||||
"semver": "^5.3.0",
|
||||
"tar": "^4.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"nopt": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
|
||||
"integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
|
||||
"requires": {
|
||||
"abbrev": "1",
|
||||
"osenv": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"npm-bundled": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz",
|
||||
"integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
|
||||
"requires": {
|
||||
"npm-normalize-package-bin": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"npm-normalize-package-bin": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
|
||||
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA=="
|
||||
},
|
||||
"npm-packlist": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
|
||||
"integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
|
||||
"requires": {
|
||||
"ignore-walk": "^3.0.1",
|
||||
"npm-bundled": "^1.0.1",
|
||||
"npm-normalize-package-bin": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"npmlog": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
|
||||
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
|
||||
"requires": {
|
||||
"are-we-there-yet": "~1.1.2",
|
||||
"console-control-strings": "~1.1.0",
|
||||
"gauge": "~2.7.3",
|
||||
"set-blocking": "~2.0.0"
|
||||
}
|
||||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
|
||||
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -878,6 +1239,25 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"os-homedir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
|
||||
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
|
||||
},
|
||||
"os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
|
||||
},
|
||||
"osenv": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
|
||||
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
|
||||
"requires": {
|
||||
"os-homedir": "^1.0.0",
|
||||
"os-tmpdir": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
@@ -957,6 +1337,14 @@
|
||||
"pg-types": "^2.1.0",
|
||||
"pgpass": "1.x",
|
||||
"semver": "4.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz",
|
||||
"integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"pg-connection-string": {
|
||||
@@ -1005,11 +1393,6 @@
|
||||
"split": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"popper.js": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
|
||||
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
|
||||
},
|
||||
"postgres-array": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||
@@ -1037,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",
|
||||
@@ -1077,6 +1466,17 @@
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||
"requires": {
|
||||
"deep-extend": "^0.6.0",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
@@ -1103,6 +1503,38 @@
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
|
||||
},
|
||||
"require_optional": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
|
||||
"integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"resolve-from": "^2.0.0",
|
||||
"semver": "^5.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"resolve-from": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
|
||||
"integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=",
|
||||
"dev": true
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
@@ -1113,16 +1545,25 @@
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"saslprep": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
|
||||
"integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"sparse-bitfield": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz",
|
||||
"integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=",
|
||||
"dev": true
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
|
||||
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
|
||||
},
|
||||
"send": {
|
||||
"version": "0.17.1",
|
||||
@@ -1186,6 +1627,26 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
|
||||
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
|
||||
},
|
||||
"sparse-bitfield": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
|
||||
"integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"memory-pager": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"spectre.css": {
|
||||
"version": "0.5.8",
|
||||
"resolved": "https://registry.npmjs.org/spectre.css/-/spectre.css-0.5.8.tgz",
|
||||
"integrity": "sha512-3N4WocWY+Dl6b3e5v3nsZYyp+VSDcBfGDzyyHw/H78ie9BoAhHkxmrhLxo9y8RadxYzVrPjfPdlev3hXEUzR2w=="
|
||||
},
|
||||
"split": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
|
||||
@@ -1233,6 +1694,11 @@
|
||||
"ansi-regex": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
@@ -1241,6 +1707,20 @@
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"tar": {
|
||||
"version": "4.4.13",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
|
||||
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
|
||||
"requires": {
|
||||
"chownr": "^1.1.1",
|
||||
"fs-minipass": "^1.2.5",
|
||||
"minipass": "^2.8.6",
|
||||
"minizlib": "^1.2.1",
|
||||
"mkdirp": "^0.5.0",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"thenify": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz",
|
||||
@@ -1357,6 +1837,43 @@
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
|
||||
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
|
||||
},
|
||||
"wide-align": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
|
||||
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
|
||||
"requires": {
|
||||
"string-width": "^1.0.2 || 2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
|
||||
},
|
||||
"string-width": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
||||
"requires": {
|
||||
"is-fullwidth-code-point": "^2.0.0",
|
||||
"strip-ansi": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||
"requires": {
|
||||
"ansi-regex": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
@@ -1420,6 +1937,11 @@
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
||||
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
||||
},
|
||||
"yargonaut": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/yargonaut/-/yargonaut-1.1.4.tgz",
|
||||
|
||||
22
package.json
@@ -1,16 +1,22 @@
|
||||
{
|
||||
"name": "typex",
|
||||
"version": "0.3.0",
|
||||
"private": true,
|
||||
"scripts": {},
|
||||
"version": "1.2.0",
|
||||
"scripts": {
|
||||
"build": "tsc -p .",
|
||||
"start": "node out/src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ayanaware/logger": "^2.2.1",
|
||||
"@forevolve/bootstrap-dark": "^1.0.0-alpha.981",
|
||||
"@overnightjs/core": "^1.6.15",
|
||||
"@types/bcrypt": "^3.0.0",
|
||||
"@types/centra": "^2.2.0",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/express-session": "^1.17.0",
|
||||
"@types/mime": "^2.0.1",
|
||||
"@types/multer": "^1.4.3",
|
||||
"@types/semver": "^7.3.1",
|
||||
"bcrypt": "^5.0.0",
|
||||
"centra": "^2.4.2",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"ejs": "^3.0.2",
|
||||
"express": "^4.17.1",
|
||||
@@ -18,9 +24,13 @@
|
||||
"http-status-codes": "^1.4.0",
|
||||
"mime": "^2.4.4",
|
||||
"multer": "^1.4.2",
|
||||
"semver": "^7.3.2",
|
||||
"spectre.css": "^0.5.8",
|
||||
"typeorm": "^0.2.24"
|
||||
},
|
||||
"devDependencies": {
|
||||
"pg": "^8.0.3"
|
||||
"mongodb": "^3.5.8",
|
||||
"pg": "^8.0.3",
|
||||
"prettier": "2.1.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
public/assets/typex-small.png
Executable file
|
After Width: | Height: | Size: 119 KiB |
0
public/assets/typex.png
Normal file → Executable file
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
0
public/assets/typex_circle.png
Normal file → Executable file
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
0
public/assets/typex_small.png
Normal file → Executable file
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 137 KiB |
0
public/assets/typex_small_circle.png
Normal file → Executable file
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 119 KiB |
1
public/css/bootstrap-dark.min.css
vendored
@@ -1,17 +0,0 @@
|
||||
canvas{
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
#particles-js canvas{
|
||||
position:absolute;
|
||||
z-index: -1;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
background-image: url("");
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: 50% 50%; }
|
||||
|
||||
|
||||
.count-particles{
|
||||
border-radius: 0 0 3px 3px;
|
||||
}
|
||||
1227
public/css/spectre-exp.css
Executable file
597
public/css/spectre-icons.css
Executable file
@@ -0,0 +1,597 @@
|
||||
/*! Spectre.css Icons v0.5.8 | MIT License | github.com/picturepan2/spectre */
|
||||
.icon {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
font-style: normal;
|
||||
height: 1em;
|
||||
position: relative;
|
||||
text-indent: -9999px;
|
||||
vertical-align: middle;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.icon::before,
|
||||
.icon::after {
|
||||
content: "";
|
||||
display: block;
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.icon.icon-2x {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.icon.icon-3x {
|
||||
font-size: 2.4rem;
|
||||
}
|
||||
|
||||
.icon.icon-4x {
|
||||
font-size: 3.2rem;
|
||||
}
|
||||
|
||||
.accordion .icon,
|
||||
.btn .icon,
|
||||
.toast .icon,
|
||||
.menu .icon {
|
||||
vertical-align: -10%;
|
||||
}
|
||||
|
||||
.btn-lg .icon {
|
||||
vertical-align: -15%;
|
||||
}
|
||||
|
||||
.icon-arrow-down::before,
|
||||
.icon-arrow-left::before,
|
||||
.icon-arrow-right::before,
|
||||
.icon-arrow-up::before,
|
||||
.icon-downward::before,
|
||||
.icon-back::before,
|
||||
.icon-forward::before,
|
||||
.icon-upward::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-right: 0;
|
||||
height: .65em;
|
||||
width: .65em;
|
||||
}
|
||||
|
||||
.icon-arrow-down::before {
|
||||
transform: translate(-50%, -75%) rotate(225deg);
|
||||
}
|
||||
|
||||
.icon-arrow-left::before {
|
||||
transform: translate(-25%, -50%) rotate(-45deg);
|
||||
}
|
||||
|
||||
.icon-arrow-right::before {
|
||||
transform: translate(-75%, -50%) rotate(135deg);
|
||||
}
|
||||
|
||||
.icon-arrow-up::before {
|
||||
transform: translate(-50%, -25%) rotate(45deg);
|
||||
}
|
||||
|
||||
.icon-back::after,
|
||||
.icon-forward::after {
|
||||
background: currentColor;
|
||||
height: .1rem;
|
||||
width: .8em;
|
||||
}
|
||||
|
||||
.icon-downward::after,
|
||||
.icon-upward::after {
|
||||
background: currentColor;
|
||||
height: .8em;
|
||||
width: .1rem;
|
||||
}
|
||||
|
||||
.icon-back::after {
|
||||
left: 55%;
|
||||
}
|
||||
|
||||
.icon-back::before {
|
||||
transform: translate(-50%, -50%) rotate(-45deg);
|
||||
}
|
||||
|
||||
.icon-downward::after {
|
||||
top: 45%;
|
||||
}
|
||||
|
||||
.icon-downward::before {
|
||||
transform: translate(-50%, -50%) rotate(-135deg);
|
||||
}
|
||||
|
||||
.icon-forward::after {
|
||||
left: 45%;
|
||||
}
|
||||
|
||||
.icon-forward::before {
|
||||
transform: translate(-50%, -50%) rotate(135deg);
|
||||
}
|
||||
|
||||
.icon-upward::after {
|
||||
top: 55%;
|
||||
}
|
||||
|
||||
.icon-upward::before {
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
}
|
||||
|
||||
.icon-caret::before {
|
||||
border-left: .3em solid transparent;
|
||||
border-right: .3em solid transparent;
|
||||
border-top: .3em solid currentColor;
|
||||
height: 0;
|
||||
transform: translate(-50%, -25%);
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.icon-menu::before {
|
||||
background: currentColor;
|
||||
box-shadow: 0 -.35em, 0 .35em;
|
||||
height: .1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.icon-apps::before {
|
||||
background: currentColor;
|
||||
box-shadow: -.35em -.35em, -.35em 0, -.35em .35em, 0 -.35em, 0 .35em, .35em -.35em, .35em 0, .35em .35em;
|
||||
height: 3px;
|
||||
width: 3px;
|
||||
}
|
||||
|
||||
.icon-resize-horiz::before,
|
||||
.icon-resize-horiz::after,
|
||||
.icon-resize-vert::before,
|
||||
.icon-resize-vert::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-right: 0;
|
||||
height: .45em;
|
||||
width: .45em;
|
||||
}
|
||||
|
||||
.icon-resize-horiz::before,
|
||||
.icon-resize-vert::before {
|
||||
transform: translate(-50%, -90%) rotate(45deg);
|
||||
}
|
||||
|
||||
.icon-resize-horiz::after,
|
||||
.icon-resize-vert::after {
|
||||
transform: translate(-50%, -10%) rotate(225deg);
|
||||
}
|
||||
|
||||
.icon-resize-horiz::before {
|
||||
transform: translate(-90%, -50%) rotate(-45deg);
|
||||
}
|
||||
|
||||
.icon-resize-horiz::after {
|
||||
transform: translate(-10%, -50%) rotate(135deg);
|
||||
}
|
||||
|
||||
.icon-more-horiz::before,
|
||||
.icon-more-vert::before {
|
||||
background: currentColor;
|
||||
border-radius: 50%;
|
||||
box-shadow: -.4em 0, .4em 0;
|
||||
height: 3px;
|
||||
width: 3px;
|
||||
}
|
||||
|
||||
.icon-more-vert::before {
|
||||
box-shadow: 0 -.4em, 0 .4em;
|
||||
}
|
||||
|
||||
.icon-plus::before,
|
||||
.icon-minus::before,
|
||||
.icon-cross::before {
|
||||
background: currentColor;
|
||||
height: .1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.icon-plus::after,
|
||||
.icon-cross::after {
|
||||
background: currentColor;
|
||||
height: 100%;
|
||||
width: .1rem;
|
||||
}
|
||||
|
||||
.icon-cross::before {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.icon-cross::after {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.icon-cross::before,
|
||||
.icon-cross::after {
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
}
|
||||
|
||||
.icon-check::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-right: 0;
|
||||
border-top: 0;
|
||||
height: .5em;
|
||||
transform: translate(-50%, -75%) rotate(-45deg);
|
||||
width: .9em;
|
||||
}
|
||||
|
||||
.icon-stop {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.icon-stop::before {
|
||||
background: currentColor;
|
||||
height: .1rem;
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.icon-shutdown {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50%;
|
||||
border-top-color: transparent;
|
||||
}
|
||||
|
||||
.icon-shutdown::before {
|
||||
background: currentColor;
|
||||
content: "";
|
||||
height: .5em;
|
||||
top: .1em;
|
||||
width: .1rem;
|
||||
}
|
||||
|
||||
.icon-refresh::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50%;
|
||||
border-right-color: transparent;
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.icon-refresh::after {
|
||||
border: .2em solid currentColor;
|
||||
border-left-color: transparent;
|
||||
border-top-color: transparent;
|
||||
height: 0;
|
||||
left: 80%;
|
||||
top: 20%;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.icon-search::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50%;
|
||||
height: .75em;
|
||||
left: 5%;
|
||||
top: 5%;
|
||||
transform: translate(0, 0) rotate(45deg);
|
||||
width: .75em;
|
||||
}
|
||||
|
||||
.icon-search::after {
|
||||
background: currentColor;
|
||||
height: .1rem;
|
||||
left: 80%;
|
||||
top: 80%;
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
width: .4em;
|
||||
}
|
||||
|
||||
.icon-edit::before {
|
||||
border: .1rem solid currentColor;
|
||||
height: .4em;
|
||||
transform: translate(-40%, -60%) rotate(-45deg);
|
||||
width: .85em;
|
||||
}
|
||||
|
||||
.icon-edit::after {
|
||||
border: .15em solid currentColor;
|
||||
border-right-color: transparent;
|
||||
border-top-color: transparent;
|
||||
height: 0;
|
||||
left: 5%;
|
||||
top: 95%;
|
||||
transform: translate(0, -100%);
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.icon-delete::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom-left-radius: .1rem;
|
||||
border-bottom-right-radius: .1rem;
|
||||
border-top: 0;
|
||||
height: .75em;
|
||||
top: 60%;
|
||||
width: .75em;
|
||||
}
|
||||
|
||||
.icon-delete::after {
|
||||
background: currentColor;
|
||||
box-shadow: -.25em .2em, .25em .2em;
|
||||
height: .1rem;
|
||||
top: .05rem;
|
||||
width: .5em;
|
||||
}
|
||||
|
||||
.icon-share {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: .1rem;
|
||||
border-right: 0;
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.icon-share::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
height: .4em;
|
||||
left: 100%;
|
||||
top: .25em;
|
||||
transform: translate(-125%, -50%) rotate(-45deg);
|
||||
width: .4em;
|
||||
}
|
||||
|
||||
.icon-share::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-radius: 75% 0;
|
||||
border-right: 0;
|
||||
height: .5em;
|
||||
width: .6em;
|
||||
}
|
||||
|
||||
.icon-flag::before {
|
||||
background: currentColor;
|
||||
height: 1em;
|
||||
left: 15%;
|
||||
width: .1rem;
|
||||
}
|
||||
|
||||
.icon-flag::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom-right-radius: .1rem;
|
||||
border-left: 0;
|
||||
border-top-right-radius: .1rem;
|
||||
height: .65em;
|
||||
left: 60%;
|
||||
top: 35%;
|
||||
width: .8em;
|
||||
}
|
||||
|
||||
.icon-bookmark::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-top-left-radius: .1rem;
|
||||
border-top-right-radius: .1rem;
|
||||
height: .9em;
|
||||
width: .8em;
|
||||
}
|
||||
|
||||
.icon-bookmark::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-left: 0;
|
||||
border-radius: .1rem;
|
||||
height: .5em;
|
||||
transform: translate(-50%, 35%) rotate(-45deg) skew(15deg, 15deg);
|
||||
width: .5em;
|
||||
}
|
||||
|
||||
.icon-download,
|
||||
.icon-upload {
|
||||
border-bottom: .1rem solid currentColor;
|
||||
}
|
||||
|
||||
.icon-download::before,
|
||||
.icon-upload::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-right: 0;
|
||||
height: .5em;
|
||||
transform: translate(-50%, -60%) rotate(-135deg);
|
||||
width: .5em;
|
||||
}
|
||||
|
||||
.icon-download::after,
|
||||
.icon-upload::after {
|
||||
background: currentColor;
|
||||
height: .6em;
|
||||
top: 40%;
|
||||
width: .1rem;
|
||||
}
|
||||
|
||||
.icon-upload::before {
|
||||
transform: translate(-50%, -60%) rotate(45deg);
|
||||
}
|
||||
|
||||
.icon-upload::after {
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
.icon-copy::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-radius: .1rem;
|
||||
border-right: 0;
|
||||
height: .8em;
|
||||
left: 40%;
|
||||
top: 35%;
|
||||
width: .8em;
|
||||
}
|
||||
|
||||
.icon-copy::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: .1rem;
|
||||
height: .8em;
|
||||
left: 60%;
|
||||
top: 60%;
|
||||
width: .8em;
|
||||
}
|
||||
|
||||
.icon-time {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.icon-time::before {
|
||||
background: currentColor;
|
||||
height: .4em;
|
||||
transform: translate(-50%, -75%);
|
||||
width: .1rem;
|
||||
}
|
||||
|
||||
.icon-time::after {
|
||||
background: currentColor;
|
||||
height: .3em;
|
||||
transform: translate(-50%, -75%) rotate(90deg);
|
||||
transform-origin: 50% 90%;
|
||||
width: .1rem;
|
||||
}
|
||||
|
||||
.icon-mail::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: .1rem;
|
||||
height: .8em;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.icon-mail::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-right: 0;
|
||||
border-top: 0;
|
||||
height: .5em;
|
||||
transform: translate(-50%, -90%) rotate(-45deg) skew(10deg, 10deg);
|
||||
width: .5em;
|
||||
}
|
||||
|
||||
.icon-people::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50%;
|
||||
height: .45em;
|
||||
top: 25%;
|
||||
width: .45em;
|
||||
}
|
||||
|
||||
.icon-people::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50% 50% 0 0;
|
||||
height: .4em;
|
||||
top: 75%;
|
||||
width: .9em;
|
||||
}
|
||||
|
||||
.icon-message {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-radius: .1rem;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.icon-message::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom-right-radius: .1rem;
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
height: .8em;
|
||||
left: 65%;
|
||||
top: 40%;
|
||||
width: .7em;
|
||||
}
|
||||
|
||||
.icon-message::after {
|
||||
background: currentColor;
|
||||
border-radius: .1rem;
|
||||
height: .3em;
|
||||
left: 10%;
|
||||
top: 100%;
|
||||
transform: translate(0, -90%) rotate(45deg);
|
||||
width: .1rem;
|
||||
}
|
||||
|
||||
.icon-photo {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: .1rem;
|
||||
}
|
||||
|
||||
.icon-photo::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50%;
|
||||
height: .25em;
|
||||
left: 35%;
|
||||
top: 35%;
|
||||
width: .25em;
|
||||
}
|
||||
|
||||
.icon-photo::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom: 0;
|
||||
border-left: 0;
|
||||
height: .5em;
|
||||
left: 60%;
|
||||
transform: translate(-50%, 25%) rotate(-45deg);
|
||||
width: .5em;
|
||||
}
|
||||
|
||||
.icon-link::before,
|
||||
.icon-link::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 5em 0 0 5em;
|
||||
border-right: 0;
|
||||
height: .5em;
|
||||
width: .75em;
|
||||
}
|
||||
|
||||
.icon-link::before {
|
||||
transform: translate(-70%, -45%) rotate(-45deg);
|
||||
}
|
||||
|
||||
.icon-link::after {
|
||||
transform: translate(-30%, -55%) rotate(135deg);
|
||||
}
|
||||
|
||||
.icon-location::before {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50% 50% 50% 0;
|
||||
height: .8em;
|
||||
transform: translate(-50%, -60%) rotate(-45deg);
|
||||
width: .8em;
|
||||
}
|
||||
|
||||
.icon-location::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50%;
|
||||
height: .2em;
|
||||
transform: translate(-50%, -80%);
|
||||
width: .2em;
|
||||
}
|
||||
|
||||
.icon-emoji {
|
||||
border: .1rem solid currentColor;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.icon-emoji::before {
|
||||
border-radius: 50%;
|
||||
box-shadow: -.17em -.1em, .17em -.1em;
|
||||
height: .15em;
|
||||
width: .15em;
|
||||
}
|
||||
|
||||
.icon-emoji::after {
|
||||
border: .1rem solid currentColor;
|
||||
border-bottom-color: transparent;
|
||||
border-radius: 50%;
|
||||
border-right-color: transparent;
|
||||
height: .5em;
|
||||
transform: translate(-50%, -40%) rotate(-135deg);
|
||||
width: .5em;
|
||||
}
|
||||
3718
public/css/spectre.css
Executable file
483
public/js/dashboard.js
Executable file
@@ -0,0 +1,483 @@
|
||||
const TypeX = {
|
||||
currentImagePage: 0,
|
||||
pagedNumbers: [],
|
||||
currentID: null
|
||||
}
|
||||
function whitespace(str) {
|
||||
return str === null || str.match(/^ *$/) !== null;
|
||||
}
|
||||
|
||||
function showAlert(type, message) {
|
||||
if (type === 'error') {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Oops...',
|
||||
text: message,
|
||||
footer: 'Try again later...'
|
||||
})
|
||||
} else if (type === 'success') {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Success',
|
||||
text: message,
|
||||
footer: 'You did it!'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function copyText(text) {
|
||||
var input = document.createElement('textarea');
|
||||
input.innerHTML = text;
|
||||
document.body.appendChild(input);
|
||||
input.select();
|
||||
var result = document.execCommand('copy');
|
||||
document.body.removeChild(input);
|
||||
return result;
|
||||
}
|
||||
|
||||
async function redoImageGrid(page, mode = null) {
|
||||
if (!page && !mode) return;
|
||||
document.getElementById('typexImages').innerHTML = '';
|
||||
let url = '';
|
||||
if (mode === 'prev') {
|
||||
if (TypeX.currentImagePage === 0) {
|
||||
url = '/api/images/user/pages?page=0';
|
||||
TypeX.currentImagePage = 0;
|
||||
} else {
|
||||
url = `/api/images/user/pages?page=${TypeX.currentImagePage - 1}`;
|
||||
TypeX.currentImagePage--;
|
||||
} //could be better :DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
|
||||
} else if (mode === 'next') {
|
||||
if (TypeX.pagedNumbers[TypeX.pagedNumbers.length - 1] <= TypeX.currentImagePage + 1) {
|
||||
url = `/api/images/user/pages?page=${TypeX.pagedNumbers[TypeX.pagedNumbers.length - 1]}`
|
||||
TypeX.currentImagePage = TypeX.pagedNumbers[TypeX.pagedNumbers.length - 1];
|
||||
} else {
|
||||
url = `/api/images/user/pages?page=${TypeX.currentImagePage + 1}`
|
||||
TypeX.currentImagePage++;
|
||||
}
|
||||
} else if (mode === 'normal') {
|
||||
url = `/api/images/user/pages?page=${page}`;
|
||||
TypeX.currentImagePage = Number(page);
|
||||
}
|
||||
$("#typexImagePaginationDropdown").val(TypeX.currentImagePage);
|
||||
const resp = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
const json = await resp.json();
|
||||
if (json.error || json.code) return showAlert('error', json.error);
|
||||
try {
|
||||
json.page.forEach(image => {
|
||||
$('#typexImages').append(`
|
||||
<div class="column col-4">
|
||||
<div class="card">
|
||||
<div class="card-image">
|
||||
<img src="${image.url}" class="img-responsive" onclick="document.getElementById('modal-image-mngt-${image.id}').classList.add('active')">
|
||||
<div class="modal" id="modal-image-mngt-${image.id}">
|
||||
<a href="#close" class="modal-overlay" aria-label="Close" onclick="document.getElementById('modal-image-mngt-${image.id}').classList.remove('active')"></a>
|
||||
<div class="modal-container bg-dark">
|
||||
<div class="modal-header">
|
||||
<a href="#close" class="btn btn-clear float-right text-light" aria-label="Close" onclick="document.getElementById('modal-image-mngt-${image.id}').classList.remove('active')"></a>
|
||||
<div class="modal-title text-light h5">Manage Image ${image.id}</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
This image is viewable at <a href="${image.url}">${image.url}</a> and has <b>${image.views}</b> views.
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" type="button" onclick="deleteImage('${image.id}')">Delete Image</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
} catch (e) {
|
||||
document.getElementById('emptyImages').innerHTML = `
|
||||
<div class="empty bg-dark">
|
||||
<div class="empty-icon">
|
||||
<i class="icon icon-photo"></i>
|
||||
</div>
|
||||
<p class="empty-title h5">You have no imaages</p>
|
||||
<p class="empty-subtitle">Use the API to start uploading!</p>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('typexImagePagination').innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('updateImages').addEventListener('click', async () => {
|
||||
redoImageGrid('0', 'normal');
|
||||
document.getElementById('emptyImages').innerHTML = '';
|
||||
document.getElementById('typexImagePagination').innerHTML = '';
|
||||
|
||||
const resp = await fetch('/api/images/user/pages', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
const json = await resp.json();
|
||||
try {
|
||||
$('#typexImagePagination').append(`
|
||||
<li class="page-item">
|
||||
<a class="page-link" aria-label="First" onclick="redoImageGrid('0', 'normal')">
|
||||
First
|
||||
</a>
|
||||
</li>`);
|
||||
$('#typexImagePagination').append(`
|
||||
<li class="page-item">
|
||||
<a class="page-link" aria-label="Previous" onclick="redoImageGrid(null, 'prev')">
|
||||
Prev
|
||||
</a>
|
||||
</li>`);
|
||||
$('#typexImagePagination').append(`
|
||||
<li class="page-item">
|
||||
<select class="form-select" id="typexImagePaginationDropdown">
|
||||
</select>
|
||||
</li>`)
|
||||
TypeX.pagedNumbers = json.pagedNums;
|
||||
json.pagedNums.forEach(p => {
|
||||
$('#typexImagePaginationDropdown').append(`
|
||||
<option onclick="redoImageGrid('${p}', 'normal')" value="${p}">${p + 1}</option>
|
||||
`)
|
||||
});
|
||||
$('#typexImagePagination').append(`
|
||||
<li class="page-item">
|
||||
<a onclick="redoImageGrid(null, 'next')">
|
||||
Next
|
||||
</a>
|
||||
</li>`);
|
||||
$('#typexImagePagination').append(`
|
||||
<li class="page-item">
|
||||
<a onclick="redoImageGrid(TypeX.pagedNumbers[TypeX.pagedNumbers.length-1], 'normal')">
|
||||
Last
|
||||
</a>
|
||||
</li>`);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('updateStatistics').addEventListener('click', async () => {
|
||||
const resp = await fetch('/api/images/statistics', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
const json = await resp.json();
|
||||
try {
|
||||
document.getElementById('statsDescription').innerHTML = `You have an average of <b>${Math.floor(json.average).toLocaleString()} views</b> on your images, you have <b>${json.totalViews.toLocaleString()} views total</b>, you currently have <b>${json.images.toLocaleString()} images</b>!`
|
||||
document.getElementById('statsLeaderboardImages').innerHTML = '';
|
||||
document.getElementById('statsLeaderboardImageViews').innerHTML = '';
|
||||
for (let i = 0; i < json.table.images.length; i++) {
|
||||
const c = json.table.images[i];
|
||||
$('#statsLeaderboardImages').append(`
|
||||
<tr>
|
||||
<th>${i + 1}</th>
|
||||
<td>${c.username}</td>
|
||||
<td>${c.count.toLocaleString()}</td>
|
||||
</tr>
|
||||
`)
|
||||
}
|
||||
for (let i = 0; i < json.table.views.length; i++) {
|
||||
const c = json.table.views[i];
|
||||
$('#statsLeaderboardImageViews').append(`
|
||||
<tr>
|
||||
<th>${i + 1}</th>
|
||||
<td>${c.username}</td>
|
||||
<td>${c.count.toLocaleString()}</td>
|
||||
</tr>
|
||||
`)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('updateShortens').addEventListener('click', async () => {
|
||||
const resp = await fetch('/api/shortens', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
const json = await resp.json();
|
||||
try {
|
||||
document.getElementById('shortensTableShortens').innerHTML = '';
|
||||
for (const shorten of json) {
|
||||
$('#shortensTableShortens').append(`
|
||||
<tr>
|
||||
<th>${shorten.id}</th>
|
||||
<td><a href="${shorten.origin}">${shorten.origin}</a></td>
|
||||
<td><a href="${shorten.url}">${shorten.url}</a></td>
|
||||
</tr>
|
||||
`)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const deleteImage = (id, url) => {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: `You are proceeding to delete image (${id}), you will not be able to recover it!`,
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, delete it.'
|
||||
}).then(async (result) => {
|
||||
if (result.value) {
|
||||
try {
|
||||
const res = await fetch('/api/images/' + id, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
try {
|
||||
const json = await res.json();
|
||||
if (json.error || json.code) return showAlert('error', json.error)
|
||||
else {
|
||||
Swal.fire(
|
||||
'Deleted!',
|
||||
`Deleted image (${id}) successfully.`,
|
||||
'success'
|
||||
);
|
||||
document.getElementById(`modal-image-mngt-${id}`).classList.remove('active')
|
||||
redoImageGrid('0', 'normal')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
const deleteSpecificUser = (id, username) => {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: `You are proceeding to delete user ${username} (${id}), you will not be able to recover them!`,
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: `Yes, delete ${username}.`
|
||||
}).then(async (result) => {
|
||||
if (result.value) {
|
||||
try {
|
||||
const res = await fetch('/api/user/' + id, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
try {
|
||||
const json = await res.json();
|
||||
if (json.error || json.code) return showAlert('error', json.error)
|
||||
else {
|
||||
Swal.fire(
|
||||
'Deleted!',
|
||||
`Deleted user ${username} (${id}) successfully.`,
|
||||
'success'
|
||||
);
|
||||
window.location.href = '/'
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
const saveUser = (id) => {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: "You are proceeding to edit your user.",
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, save changes!'
|
||||
}).then(async (result) => {
|
||||
if (result.value) {
|
||||
const username = document.getElementById('usernameSave').value;
|
||||
const password = document.getElementById('passwordSave').value;
|
||||
if (whitespace(username)) return showAlert('error', 'Please input a username.')
|
||||
const res = await fetch(`/api/users/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
payload: 'USER_EDIT',
|
||||
username,
|
||||
password
|
||||
})
|
||||
});
|
||||
try {
|
||||
const json = await res.json();
|
||||
if (json.error || json.code) return showAlert('error', json.error)
|
||||
else {
|
||||
Swal.fire(
|
||||
'Saved Changes!',
|
||||
'Changes were saved successfully!',
|
||||
'success'
|
||||
);
|
||||
window.location.href = '/'
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
async function shortURL(token, url) {
|
||||
if (whitespace(url)) return showAlert('error', 'Please input a URL.')
|
||||
const res = await fetch('/api/shorten', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'authorization': token
|
||||
},
|
||||
body: JSON.stringify({
|
||||
url
|
||||
})
|
||||
});
|
||||
try {
|
||||
let te = await res.text();
|
||||
Swal.fire(
|
||||
'URL Shortened!',
|
||||
`Shorten: <a target="_blank" href="${te}">${te}</a>`,
|
||||
'success'
|
||||
);
|
||||
return;
|
||||
} catch (e) {
|
||||
if (e.message.startsWith('Unexpected token < in JSON at position')) {
|
||||
let te = await res.text();
|
||||
Swal.fire(
|
||||
'URL Shortened!',
|
||||
`Shorten: <a target="_blank" href="${te}">${te}</a>`,
|
||||
'success'
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const copyToken = (token) => {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: "You are proceeding to copy your token, make sure NO ONE sees it.",
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3`085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, copy it!'
|
||||
}).then((result) => {
|
||||
if (result.value) {
|
||||
copyText(token);
|
||||
Swal.fire(
|
||||
'Copied!',
|
||||
'Your API Token has been copied.',
|
||||
'success'
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
function regenToken(id) {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: "You are proceeding to regenerate your token, remember all apps using your current one will stop working.",
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, regenerate it!'
|
||||
}).then(async (result) => {
|
||||
if (result.value) {
|
||||
const res = await fetch(`/api/users/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
payload: 'USER_TOKEN_RESET'
|
||||
})
|
||||
});
|
||||
try {
|
||||
const json = await res.json();
|
||||
if (json.error || json.code) return showAlert('error', json.error)
|
||||
else {
|
||||
Swal.fire(
|
||||
'Regenerated!',
|
||||
'Your API Token has been regenerated.',
|
||||
'success'
|
||||
);
|
||||
return window.location.href = '/'
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
async function createUser() {
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
if (whitespace(username)) return showAlert('error', 'Please input a username.')
|
||||
const res = await fetch('/api/users', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username,
|
||||
password,
|
||||
administrator: document.getElementById('administrator').checked
|
||||
})
|
||||
});
|
||||
try {
|
||||
const json = await res.json();
|
||||
if (json.error || json.code) return showAlert('error', json.error)
|
||||
else {
|
||||
showAlert('success', `Created user ${json.username} (${json.id})`)
|
||||
return window.location.href = '/'
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('addUser').addEventListener('click', async () => {
|
||||
if (document.getElementById('administrator').checked) {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: "You are proceeding to create a user with administrator permissions, they can do whatever they want!",
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, create user!'
|
||||
}).then(async (result) => {
|
||||
if (result.value) {
|
||||
createUser()
|
||||
}
|
||||
});
|
||||
} else {
|
||||
createUser();
|
||||
}
|
||||
})
|
||||
1
scripts/update.sh
Executable file
@@ -0,0 +1 @@
|
||||
echo "Updating Zipline\n\n\n\n\n" && git pull && tsc -p .
|
||||
596
src/controllers/APIController.ts
Normal file → Executable file
@@ -1,128 +1,564 @@
|
||||
import { OK, BAD_REQUEST, FORBIDDEN } from 'http-status-codes';
|
||||
import { Controller, Middleware, Get, Post, Put, Delete, Patch } from '@overnightjs/core';
|
||||
import { Request, Response } from 'express';
|
||||
import config from '../../config.json';
|
||||
import { ORMHandler } from '..';
|
||||
import { randomId, getUser, getImage } from '../util';
|
||||
import { createReadStream, createWriteStream, unlinkSync, existsSync, mkdirSync } from 'fs'
|
||||
import multer from 'multer'
|
||||
import { getExtension } from 'mime';
|
||||
import { User } from '../entities/User';
|
||||
import { sep } from 'path';
|
||||
import { cookiesForAPI } from '../middleware/cookiesForAPI';
|
||||
const upload = multer({ dest: config.upload.tempDir });
|
||||
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";
|
||||
|
||||
@Controller('api')
|
||||
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 upload = multer({ dest: config.uploader.temp });
|
||||
|
||||
@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) {
|
||||
if (req.headers['authorization'] === config.administrator.authorization) return res.status(BAD_REQUEST).json({ code: BAD_REQUEST, message: "You can't upload files with the administrator account." })
|
||||
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 file = req.file;
|
||||
const id = randomId(config.upload.fileLength);
|
||||
const extension = getExtension(file.mimetype);
|
||||
const source = createReadStream(file.path);
|
||||
if (!existsSync(config.upload.uploadDir)) mkdirSync(config.upload.uploadDir);
|
||||
const destination = createWriteStream(`${config.upload.uploadDir}${sep}${id}.${extension}`);
|
||||
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.",
|
||||
});
|
||||
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()}`
|
||||
);
|
||||
source.pipe(destination, { end: false });
|
||||
source.on("end", function () {
|
||||
unlinkSync(file.path);
|
||||
unlinkSync(req.file.path);
|
||||
});
|
||||
getImage(this.orm, `${req.protocol}://${req.headers['host']}/u/${id}.${extension}`, user.id)
|
||||
return res.status(200).send(`${req.protocol}://${req.headers['host']}/u/${id}.${extension}`)
|
||||
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('user')
|
||||
@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 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}`
|
||||
);
|
||||
}
|
||||
|
||||
@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 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})`
|
||||
);
|
||||
// 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}`
|
||||
);
|
||||
}
|
||||
|
||||
@Get("users")
|
||||
@Middleware(cookiesForAPI)
|
||||
private async newUser(req: Request, res: Response) {
|
||||
if (!req.session.user.administrator) return res.status(FORBIDDEN).json({ code: FORBIDDEN, message: 'Unauthorized' });
|
||||
const data = req.body;
|
||||
private async getUsers(req: Request, res: Response) {
|
||||
if (!req.session.user.administrator)
|
||||
return res
|
||||
.status(FORBIDDEN)
|
||||
.json({ code: FORBIDDEN, message: "Unauthorized" });
|
||||
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: data.password, administrator: data.administrator }))
|
||||
return res.status(200).json(user);
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
@Patch('user')
|
||||
@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" });
|
||||
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`
|
||||
);
|
||||
return res.status(200).json(user);
|
||||
} catch (e) {
|
||||
return res
|
||||
.status(BAD_REQUEST)
|
||||
.json({ error: "Could not create user: " + e.message });
|
||||
}
|
||||
}
|
||||
|
||||
@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.",
|
||||
});
|
||||
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,
|
||||
})
|
||||
);
|
||||
return res.status(200).json({ success: true });
|
||||
} catch (e) {
|
||||
return res
|
||||
.status(BAD_REQUEST)
|
||||
.json({ error: `Couldn't create user: ${e.message}` });
|
||||
}
|
||||
}
|
||||
|
||||
@Patch("users/:id")
|
||||
@Middleware(cookiesForAPI)
|
||||
private async patchUser(req: Request, res: Response) {
|
||||
const data = req.body;
|
||||
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 edit user: user doesnt exist" })
|
||||
this.orm.repos.user.update({ id: req.session.user.id }, data)
|
||||
return res.status(200).json(user);
|
||||
} catch (e) {
|
||||
return res.status(BAD_REQUEST).json({ error: "Could not edit user: " + e.message })
|
||||
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`
|
||||
);
|
||||
return res.status(200).json(user);
|
||||
} catch (e) {
|
||||
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);
|
||||
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.`
|
||||
);
|
||||
return res.status(200).json(user);
|
||||
} catch (e) {
|
||||
return res
|
||||
.status(BAD_REQUEST)
|
||||
.json({ error: "Could not reset password: " + e.message });
|
||||
}
|
||||
} 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" });
|
||||
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`
|
||||
);
|
||||
return res.status(200).json(user);
|
||||
} catch (e) {
|
||||
return res
|
||||
.status(BAD_REQUEST)
|
||||
.json({ error: "Could not regen token: " + e.message });
|
||||
}
|
||||
} else {
|
||||
console.log(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Delete('user')
|
||||
@Delete("users/:id")
|
||||
@Middleware(cookiesForAPI)
|
||||
private async deleteUser(req: Request, res: Response) {
|
||||
const q = req.query.user;
|
||||
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(q) || req.session.user.id })
|
||||
if (!user) return res.status(BAD_REQUEST).json({ error: "Could not delete user: user doesnt exist" })
|
||||
this.orm.repos.user.delete({ id: Number(q) || req.session.user.id })
|
||||
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`
|
||||
);
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
@Post('token')
|
||||
@Get("images")
|
||||
@Middleware(cookiesForAPI)
|
||||
private async postToken(req: Request, res: Response) {
|
||||
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" })
|
||||
user.token = randomId(config.user.tokenLength);
|
||||
req.session.user = user;
|
||||
await this.orm.repos.user.save(user);
|
||||
return res.status(200).json(user);
|
||||
} catch (e) {
|
||||
return res.status(BAD_REQUEST).json({ error: "Could not regen token: " + e.message })
|
||||
}
|
||||
}
|
||||
|
||||
@Get('images/user')
|
||||
@Middleware(cookiesForAPI)
|
||||
private async imagesUser(req: Request, res: Response) {
|
||||
const userId = req.query.user;
|
||||
const all = await this.orm.repos.image.find({ where: { user: req.session.user.id }, order: { id: 'ASC' } });
|
||||
private async allImages(req: Request, res: Response) {
|
||||
const all = await this.orm.repos.image.find({
|
||||
where: { user: req.session.user.id },
|
||||
order: { id: "ASC" },
|
||||
});
|
||||
return res.status(200).json(all);
|
||||
}
|
||||
|
||||
@Delete('images')
|
||||
@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 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" },
|
||||
});
|
||||
images.push({
|
||||
username: user.username,
|
||||
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,
|
||||
});
|
||||
}
|
||||
return res.status(200).json({
|
||||
totalViews,
|
||||
images: all.length,
|
||||
average: totalViews / all.length,
|
||||
table: {
|
||||
images: images.sort((a, b) => b.count - a.count),
|
||||
views: views.sort((a, b) => b.count - a.count),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@Delete("images/:id")
|
||||
@Middleware(cookiesForAPI)
|
||||
private async deleteImage(req: Request, res: Response) {
|
||||
const img = req.query.image;
|
||||
try {
|
||||
let image = await this.orm.repos.image.findOne({ id: Number(img) })
|
||||
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(img) });
|
||||
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.upload.uploadDir}${sep}${url.pathname.slice(3)}`);
|
||||
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)}`
|
||||
);
|
||||
return res.status(200).json(image);
|
||||
} 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 image: " + e.message });
|
||||
}
|
||||
}
|
||||
|
||||
@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" },
|
||||
});
|
||||
return res.status(200).json(all);
|
||||
}
|
||||
|
||||
@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 paged = [];
|
||||
const pagedNums = [];
|
||||
while (all.length) paged.push(all.splice(0, 25));
|
||||
for (let x = 0; x < paged.length; x++) pagedNums.push(x);
|
||||
if (!req.query.page) return res.status(200).json({ pagedNums });
|
||||
else return res.status(200).json({ page: paged[Number(req.query.page)] });
|
||||
}
|
||||
|
||||
@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" },
|
||||
});
|
||||
return res.status(200).json(all);
|
||||
}
|
||||
|
||||
@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" },
|
||||
});
|
||||
return res.status(200).json(all);
|
||||
}
|
||||
|
||||
@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" },
|
||||
});
|
||||
return res.status(200).json(all);
|
||||
}
|
||||
|
||||
@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" },
|
||||
});
|
||||
return res.status(200).json(all);
|
||||
}
|
||||
|
||||
@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);
|
||||
return res.status(200).json({
|
||||
memory,
|
||||
uploadedStatistics: {
|
||||
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(),
|
||||
},
|
||||
zipline: {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
129
src/controllers/IndexController.ts
Normal file → Executable file
@@ -1,66 +1,117 @@
|
||||
import { OK, BAD_REQUEST, FORBIDDEN } from 'http-status-codes';
|
||||
import { Controller, Middleware, Get, Post, Put, Delete } from '@overnightjs/core';
|
||||
import { Request, Response } from 'express';
|
||||
import config from '../../config.json';
|
||||
import { ORMHandler } from '..';
|
||||
import { randomId, getUser, getImage } from '../util';
|
||||
import { createReadStream, createWriteStream, unlinkSync, existsSync, mkdirSync } from 'fs'
|
||||
import multer from 'multer'
|
||||
import { getExtension } from 'mime';
|
||||
import { User } from '../entities/User';
|
||||
import { cookies } from '../middleware/cookies';
|
||||
const upload = multer({ dest: 'temp/' });
|
||||
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";
|
||||
|
||||
@Controller('')
|
||||
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")
|
||||
);
|
||||
|
||||
@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' } });
|
||||
return res.render('index', { user: req.session.user, images, users, config })
|
||||
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;
|
||||
}
|
||||
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`
|
||||
);
|
||||
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.administrator.password) {
|
||||
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',
|
||||
password: config.administrator.password,
|
||||
token: config.administrator.authorization,
|
||||
administrator: true
|
||||
}
|
||||
res.cookie('typex_user', req.session.user.id, { maxAge: 1036800000 });
|
||||
return res.redirect('/')
|
||||
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("/");
|
||||
}
|
||||
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 (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");
|
||||
shorten.clicks++;
|
||||
this.orm.repos.shorten.save(shorten);
|
||||
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");
|
||||
return res.send(note.content);
|
||||
}
|
||||
|
||||
public set(orm: ORMHandler) {
|
||||
this.orm = orm;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
22
src/core/Console.ts
Normal file → Executable 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();
|
||||
|
||||
30
src/entities/Image.ts
Normal file → Executable file
@@ -1,19 +1,23 @@
|
||||
import { Column, Entity, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm";
|
||||
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;
|
||||
|
||||
set(options: { url: string, user: number }) {
|
||||
this.url = options.url;
|
||||
this.user = options.user;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
36
src/entities/Note.ts
Executable file
@@ -0,0 +1,36 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity()
|
||||
export class Note {
|
||||
@PrimaryGeneratedColumn({ type: "bigint" })
|
||||
id: number;
|
||||
|
||||
@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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
35
src/entities/Shorten.ts
Executable file
@@ -0,0 +1,35 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity()
|
||||
export class Shorten {
|
||||
@PrimaryGeneratedColumn({ type: "bigint" })
|
||||
id: number;
|
||||
|
||||
@Column("text")
|
||||
origin: string;
|
||||
|
||||
@Column("text")
|
||||
url: string;
|
||||
|
||||
@Column("text", { default: "" })
|
||||
key: string;
|
||||
|
||||
@Column("bigint")
|
||||
user: number;
|
||||
|
||||
@Column("bigint", { default: 0 })
|
||||
views: 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;
|
||||
}
|
||||
}
|
||||
26
src/entities/User.ts
Normal file → Executable file
@@ -1,10 +1,22 @@
|
||||
import { Column, Entity, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { randomId } from "../util";
|
||||
import config from '../../config.json';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { randomId, 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);
|
||||
}
|
||||
|
||||
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")
|
||||
@@ -19,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.user.tokenLength)
|
||||
this.token = randomId(config.core.userTokenLength);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
67
src/index.ts
Normal file → Executable file
@@ -1,19 +1,39 @@
|
||||
import "./core/Console";
|
||||
import {
|
||||
Repository,
|
||||
Connection,
|
||||
createConnection,
|
||||
ConnectionOptions,
|
||||
} from "typeorm";
|
||||
import { Repository, Connection, createConnection } from "typeorm";
|
||||
import { User } from "./entities/User";
|
||||
import { TypeXServer } from "./server";
|
||||
import config from "../config.json";
|
||||
import { ZiplineServer } from "./server";
|
||||
import Logger from "@ayanaware/logger";
|
||||
import { Image } from "./entities/Image";
|
||||
import { findFile } from "./util";
|
||||
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";
|
||||
|
||||
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")
|
||||
);
|
||||
|
||||
if (!config.uploader?.route) {
|
||||
Logger.get("Zipline.Config").error(
|
||||
`Missing needed property on configuration: upload.route`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
export interface ORMRepos {
|
||||
user?: Repository<User>;
|
||||
image?: Repository<Image>;
|
||||
shorten?: Repository<Shorten>;
|
||||
note?: Repository<Note>;
|
||||
}
|
||||
|
||||
export interface ORMHandler {
|
||||
@@ -21,19 +41,44 @@ export interface ORMHandler {
|
||||
connection: Connection;
|
||||
}
|
||||
|
||||
const pk = JSON.parse(
|
||||
readFileSync(findFile("package.json", process.cwd()), "utf8")
|
||||
);
|
||||
|
||||
(async () => {
|
||||
const connection = await createConnection(config.orm as ConnectionOptions);
|
||||
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,
|
||||
repos: {
|
||||
user: connection.getRepository(User),
|
||||
image: connection.getRepository(Image),
|
||||
shorten: connection.getRepository(Shorten),
|
||||
note: connection.getRepository(Note),
|
||||
},
|
||||
};
|
||||
|
||||
if (orm.connection.isConnected)
|
||||
Logger.get(Connection).info(
|
||||
`Successfully initialized database type: ${config.orm.type}`
|
||||
`Successfully initialized database type: ${config.database.type}`
|
||||
);
|
||||
const server = new TypeXServer(orm);
|
||||
const server = new ZiplineServer(orm);
|
||||
server.start();
|
||||
Logger.get("Interval").info("Starting Notes interval");
|
||||
notes(orm);
|
||||
})();
|
||||
|
||||
23
src/interval.ts
Executable file
@@ -0,0 +1,23 @@
|
||||
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`
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
57
src/middleware/cookies.ts
Normal file → Executable file
@@ -1,24 +1,41 @@
|
||||
import Logger from "@ayanaware/logger";
|
||||
import { Request, Response } from "express";
|
||||
import config from '../../config.json';
|
||||
import { getConnection } from 'typeorm';
|
||||
import { getConnection } from "typeorm";
|
||||
import { User } from "../entities/User";
|
||||
import { findFile } from "../util";
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
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")
|
||||
);
|
||||
|
||||
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 (req.cookies.typex_user === 0) req.session.user = {
|
||||
id: 0,
|
||||
username: 'administrator',
|
||||
password: config.administrator.password,
|
||||
token: config.administrator.authorization,
|
||||
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();
|
||||
}
|
||||
|
||||
64
src/middleware/cookiesForAPI.ts
Normal file → Executable file
@@ -1,21 +1,51 @@
|
||||
import Logger from "@ayanaware/logger";
|
||||
import { Request, Response } from "express";
|
||||
import config from '../../config.json';
|
||||
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";
|
||||
|
||||
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")
|
||||
);
|
||||
|
||||
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 (req.cookies.typex_user === 0) req.session.user = {
|
||||
id: 0,
|
||||
username: 'administrator',
|
||||
password: config.administrator.password,
|
||||
token: config.administrator.authorization,
|
||||
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();
|
||||
}
|
||||
|
||||
103
src/server.ts
Normal file → Executable file
@@ -1,9 +1,7 @@
|
||||
import * as bodyParser from "body-parser";
|
||||
import { Server } from "@overnightjs/core";
|
||||
import { Connection } from "typeorm";
|
||||
import { ORMHandler } from ".";
|
||||
import Logger from "@ayanaware/logger";
|
||||
import config from "../config.json";
|
||||
import * as express from "express";
|
||||
import * as http from "http";
|
||||
import * as https from "https";
|
||||
@@ -12,23 +10,78 @@ import session from "express-session";
|
||||
import cookies from "cookie-parser";
|
||||
import { APIController } from "./controllers/APIController";
|
||||
import { IndexController } from "./controllers/IndexController";
|
||||
import { findFile } from "./util";
|
||||
|
||||
export class TypeXServer extends Server {
|
||||
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")
|
||||
);
|
||||
|
||||
export class ZiplineServer extends Server {
|
||||
constructor(orm: ORMHandler) {
|
||||
super();
|
||||
var p = "loopback";
|
||||
if (config.core.trustedProxy) {
|
||||
p += "," + config.core.trustedProxy;
|
||||
}
|
||||
this.app.set("trust proxy", p);
|
||||
this.app.set("view engine", "ejs");
|
||||
this.app.use(
|
||||
session({
|
||||
secret: config.sessionSecret,
|
||||
secret: config.core.sessionSecret,
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
})
|
||||
);
|
||||
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
|
||||
}`,
|
||||
});
|
||||
if (!upload) return next();
|
||||
upload.views++;
|
||||
orm.repos.image.save(upload);
|
||||
return next();
|
||||
});
|
||||
this.app.use(cookies());
|
||||
this.app.set("view engine", "ejs");
|
||||
this.app.use("/u", express.static("uploads"));
|
||||
try {
|
||||
this.app.use(
|
||||
config.uploader.route,
|
||||
express.static(config.uploader.upload)
|
||||
);
|
||||
} catch (e) {
|
||||
Logger.get("TypeX.Routes").error(
|
||||
`Could not formulate upload static route`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
this.app.use("/public", express.static("public"));
|
||||
this.app.use(bodyParser.json());
|
||||
this.app.use(bodyParser.urlencoded({ extended: true }));
|
||||
this.app.use(async (req, res, next) => {
|
||||
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>"
|
||||
}`
|
||||
);
|
||||
return next();
|
||||
});
|
||||
|
||||
this.setupControllers(orm);
|
||||
}
|
||||
|
||||
@@ -36,30 +89,46 @@ export class TypeXServer 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.use((err, req, res, next) => {
|
||||
Logger.get(this.app).error(err);
|
||||
return res.status(500).render("error");
|
||||
});
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
// this.app.listen(port, () => {
|
||||
// Logger.get(TypeXServer).info('Started server on port ' + port);
|
||||
// })
|
||||
let server;
|
||||
if (config.site.protocol === "https") {
|
||||
if (config.core.secure) {
|
||||
try {
|
||||
const creds = {
|
||||
key: fs.readFileSync(config.site.ssl.key, "utf-8"),
|
||||
cert: fs.readFileSync(config.site.ssl.cert, "utf-8")
|
||||
key: fs.readFileSync(config.core.ssl.key, "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('TypeXServer.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.site.protocol === 'https' ? config.site.serveHTTPS : config.site.serveHTTP, () => {
|
||||
Logger.get(TypeXServer).info('Started server on port ' + String(config.site.protocol === 'https' ? config.site.serveHTTPS : config.site.serveHTTP));
|
||||
})
|
||||
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
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
24
src/structures/ConsoleFormatter.ts
Normal file → Executable 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")} `;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
56
src/structures/DiscordWebhook.ts
Executable file
@@ -0,0 +1,56 @@
|
||||
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();
|
||||
|
||||
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})`);
|
||||
}
|
||||
}
|
||||
10
src/structures/GitHub.ts
Executable file
@@ -0,0 +1,10 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
44
src/structures/ImageUtil.ts
Executable file
@@ -0,0 +1,44 @@
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
44
src/structures/ShortenUtil.ts
Executable file
@@ -0,0 +1,44 @@
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
86
src/util.ts
Normal file → Executable file
@@ -1,33 +1,41 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { ORMHandler } from '.';
|
||||
import { User } from './entities/User';
|
||||
import { Image } from './entities/Image';
|
||||
|
||||
|
||||
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++) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -35,4 +43,52 @@ export async function getImage(orm: ORMHandler, url: string, user: number) {
|
||||
const image = await orm.repos.image.findOne({ url, user });
|
||||
if (!image) return orm.repos.image.save(new Image().set({ url, user }));
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
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 })
|
||||
);
|
||||
return image;
|
||||
}
|
||||
|
||||
export function findFile(file, directory) {
|
||||
const result = [];
|
||||
(function read(dir) {
|
||||
const files = readdirSync(dir);
|
||||
for (const file of files) {
|
||||
const filepath = join(dir, file);
|
||||
if (
|
||||
file !== ".git" &&
|
||||
file !== "node_modules" &&
|
||||
statSync(filepath).isDirectory()
|
||||
) {
|
||||
read(filepath);
|
||||
} else {
|
||||
result.push(filepath);
|
||||
}
|
||||
}
|
||||
})(directory);
|
||||
for (const f of result) {
|
||||
const base = basename(f);
|
||||
if (base === file) return f;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function checkPassword(pass: string, hash: string): boolean {
|
||||
return bcrypt.compareSync(pass, hash);
|
||||
}
|
||||
|
||||
export function hashPassword(pass: string, saltRounds: number): string {
|
||||
// console.log(bcrypt.hashSync(pass,saltRounds));
|
||||
return bcrypt.hashSync(pass, saltRounds);
|
||||
}
|
||||
|
||||
26
tsconfig.json
Normal file → Executable 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"]
|
||||
}
|
||||
|
||||
23
views/404.ejs
Executable file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<%- include('./partials/head') %>
|
||||
<meta property="og:title" content="Not Found" />
|
||||
<meta name="theme-color" content="#09122B" />
|
||||
<meta property="og:description" content="This page does not exist...">
|
||||
<title>Not Found</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="particles-js"></div>
|
||||
<div class="container h-100 d-flex justify-content-center">
|
||||
<div class="jumbotron my-auto">
|
||||
<h1 class="display-4" style="text-align: center;">404 - Not Found</h1>
|
||||
<h5 style="text-align: center;">Looks like you hit a dead end... Return <a href="/"
|
||||
style="color: #036ffc;">home</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
22
views/error.ejs
Executable file
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<%- include('./partials/head') %>
|
||||
<meta property="og:title" content="Something went wrong..." />
|
||||
<meta name="theme-color" content="#09122B" />
|
||||
<meta property="og:description" content="Something happened!">
|
||||
<title>Something went wrong...</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container h-100 d-flex justify-content-center">
|
||||
<div class="jumbotron my-auto">
|
||||
<h1 class="display-4" style="text-align: center;">500 - Internal Server Error</h1>
|
||||
<h5 style="text-align: center;">Looks like something went wrong when processing your request... Return <a
|
||||
href="/" style="color: #036ffc;">home</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
639
views/index.ejs
Normal file → Executable file
@@ -2,440 +2,257 @@
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<style>
|
||||
body,
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.typex-image-actions {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.typex-image-action {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
#copyToken {
|
||||
transition: backgroundColor .3s;
|
||||
} */
|
||||
</style>
|
||||
<%- include('./partials/head') %>
|
||||
<link rel="icon" href="<%=config.meta.favicon%>" type="image/png">
|
||||
<title><%= config.meta.title %> - Dashboard</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="particles-js"></div>
|
||||
<div class="container h-100 d-flex justify-content-center">
|
||||
<div class="jumbotron my-auto">
|
||||
<ul class="nav nav-pills mb-3" id="main-view-tab-list" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a style="border-radius: 50px;" class="nav-link active" id="home-tab" data-toggle="tab" href="#home"
|
||||
role="tab" aria-controls="home" aria-selected="true">Home</a>
|
||||
</li>
|
||||
<li class="nav-item" id="updateImages">
|
||||
<a style="border-radius: 50px;" class="nav-link" id="profile-tab" data-toggle="tab" href="#images"
|
||||
role="tab" aria-controls="images" aria-selected="false">Your
|
||||
Images</a>
|
||||
</li>
|
||||
<% if (user.administrator) { %>
|
||||
<li class="nav-item">
|
||||
<a style="border-radius: 50px;" class="nav-link" id="contact-tab" data-toggle="tab" href="#users"
|
||||
role="tab" aria-controls="users" aria-selected="false">Users</a>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
<div class="tab-content" id="main-view">
|
||||
<div class="tab-pane fade show active m-3" id="home" role="tabpanel" aria-labelledby="home-tab">
|
||||
<h2>Welcome back, <%= user.username %> <a class="btn btn-sm btn-danger" href="/logout"
|
||||
style="border-radius: 50px;">Logout</a>
|
||||
</h2>
|
||||
<p>You have <b><%= images.length %></b> images saved, and you
|
||||
<%= user.administrator ? 'are an administrator' : 'are not an administrator' %>.</p>
|
||||
<h4>API Token</h4>
|
||||
<button type="button" class="btn btn-sm btn-primary" id="copyToken"
|
||||
style="border-radius: 50px;">Copy</button>
|
||||
<button type="button" class="btn btn-sm btn-danger" id="regenToken"
|
||||
style="border-radius: 50px;">Regenerate</button>
|
||||
<h4 style="margin-top:12px">Update your Profile</h4>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="usernameSave">Username</label>
|
||||
<input value="<%= user.username %>" type="text" class="form-control" id="usernameSave"
|
||||
placeholder="Username">
|
||||
</div>
|
||||
<div class="text-light">
|
||||
<input type="radio" id="home-tab" name="tabs" class="tab-locator" hidden checked />
|
||||
<input type="radio" id="stats-tab" name="tabs" class="tab-locator" hidden />
|
||||
<input type="radio" id="urls-tab" name="tabs" class="tab-locator" hidden />
|
||||
<input type="radio" id="notes-tab" name="tabs" class="tab-locator" hidden />
|
||||
<input type="radio" id="images-tab" name="tabs" class="tab-locator" hidden />
|
||||
<% if (user.administrator) { %>
|
||||
<input type="radio" id="users-tab" name="tabs" class="tab-locator" hidden />
|
||||
<% } %>
|
||||
<ul class="tab tab-block" style="border:none;">
|
||||
<li class="tab-item text-light"><label for="home-tab"><a><i class="icon icon-apps"></i> Home</a></label>
|
||||
</li>
|
||||
<li class="tab-item text-light" id="updateStatistics"><label for="stats-tab"><a><i
|
||||
class="icon icon-message"></i> Statistics</a></label></li>
|
||||
<li class="tab-item text-light" id="updateShortens"><label for="urls-tab"><a><i class="icon icon-link"></i>
|
||||
URLS</a></label></li>
|
||||
<li class="tab-item text-light" id="updateNotes"><label for="notes-tab"><a><i class="icon icon-copy"></i>
|
||||
Notes</a></label></li>
|
||||
<li class="tab-item text-light" id="updateImages"><label for="images-tab"><a><i class="icon icon-photo"></i>
|
||||
Images</a></label></li>
|
||||
<% if (user.administrator) { %>
|
||||
<li class="tab-item text-light"><label for="users-tab"><a><i class="icon icon-people"></i> Users</a></label>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="passwordSave">Password</label>
|
||||
<input value="<%= user.password %>" type="password" class="form-control" id="passwordSave"
|
||||
placeholder="Password">
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" id="saveUser"
|
||||
style="border-radius: 50px; width:100%;">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="images" role="tabpanel" aria-labelledby="images-tab">
|
||||
<h3>Your Images</h3>
|
||||
<div class="container-fluid pt-2">
|
||||
<div class="card-columns" id="typexImages">
|
||||
|
||||
</div>
|
||||
<div class="tabs">
|
||||
<div class="tab-content" style="margin: 50px;">
|
||||
<div class="card bg-dark" style="border-color: #303742;">
|
||||
<div class="card-header">
|
||||
<div class="card-title h5">Welcome back, <%=user.username%>!</div>
|
||||
<% if (user.username === "administrator") { %>
|
||||
<div class="card-subtitle text-gray"><b>This account is not designed for general usage, only to
|
||||
create a user for uploading, and other administrator duties.</b></div>
|
||||
<% } else { %>
|
||||
<div class="card-subtitle text-gray">You have <b><%=images.length%></b> images.</div>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<% if (user.username !== "administrator") { %>
|
||||
<div class="card-title h5">Token</div>
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<div class="column col-6">
|
||||
<button style="margin-top:12px;width:100%" type="button"
|
||||
class="btn btn-primary input-group-btn" id="copyToken"
|
||||
onclick="copyToken('<%=user.token%>')">Copy Token</button>
|
||||
</div>
|
||||
<div class="column col-6">
|
||||
<button style="margin-top:12px;width:100%" type="button"
|
||||
class="btn btn-primary input-group-btn" id="regenToken"
|
||||
onclick="regenToken('<%=user.id%>')">Regen Token</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-title h5">Update your profile</div>
|
||||
<form>
|
||||
<label class="form-label" for="usernameSave">Username</label>
|
||||
<input class="form-input" type="text" id="usernameSave" name="username"
|
||||
placeholder="Username">
|
||||
<label class="form-label" for="passwordSave">Password</label>
|
||||
<input class="form-input" type="password" id="passwordSave" name="password"
|
||||
placeholder="Password">
|
||||
</form>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<% if (user.username === "administrator") { %>
|
||||
<a href="/logout" style="margin-top:12px;width:100%" type="button"
|
||||
class="btn btn-error input-group-btn">Logout</a>
|
||||
<% } else { %>
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<div class="column col-6">
|
||||
<button style="margin-top:12px;width:100%" type="button"
|
||||
class="btn btn-primary input-group-btn"
|
||||
onclick="saveUser('<%=user.id%>')">Update</button>
|
||||
</div>
|
||||
<div class="column col-6">
|
||||
<a href="/logout" style="margin-top:12px;width:100%" type="button"
|
||||
class="btn btn-error input-group-btn">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<% if (user.administrator) { %>
|
||||
<div class="tab-pane fade m-3" id="users" role="tabpanel" aria-labelledby="users-tab">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Username</th>
|
||||
<th scope="col">Administrator</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% users.forEach(u => { %>
|
||||
<tr>
|
||||
<th scope="row"><%= u.id %></th>
|
||||
<td><%= u.username %></td>
|
||||
<% if (u.administrator) { %>
|
||||
<td class="text-success">Yes</td>
|
||||
<% } else { %>
|
||||
<td class="text-danger">No</td>
|
||||
<% } %>
|
||||
<td>
|
||||
<% if (!u.administrator) { %>
|
||||
<button type="button" class="btn btn-sm btn-danger"
|
||||
style="border-radius: 50px;">Delete</button>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
<button style="border-radius: 50px;" type="button" class="btn btn-primary" data-toggle="modal"
|
||||
data-target="#modal">Create new
|
||||
User</button>
|
||||
<div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">New User</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="tab-content" style="margin:50px;">
|
||||
<h3>Statistics</h3>
|
||||
<h5 id="statsDescription"></h5>
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<div class="column col-6">
|
||||
<div class="card bg-dark" style="border-color: #303742; margin:12px;">
|
||||
<div class="card-header">
|
||||
<div class="card-title h5">Leaderboard (Image)</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username"
|
||||
placeholder="Username">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password"
|
||||
placeholder="Password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="administrator">
|
||||
<label class="custom-control-label"
|
||||
for="administrator">Administrator?</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="card-body">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Username</th>
|
||||
<th scope="col">Images</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="statsLeaderboardImages">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="row" style="width:100%;">
|
||||
<div class="col-sm-6">
|
||||
<button type="button" id="createUser" class="btn btn-primary"
|
||||
style="border-radius: 50px; width:100%;">Create</button>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<button type="button" class="btn btn-danger" data-dismiss="modal"
|
||||
style="border-radius: 50px; width:100%;">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column col-6">
|
||||
<div class="card bg-dark" style="border-color: #303742; margin:12px;">
|
||||
<div class="card-header">
|
||||
<div class="card-title h5">Leaderboard (Views)</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Username</th>
|
||||
<th scope="col">Views</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="statsLeaderboardImageViews">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="tab-content" style="margin:50px;">
|
||||
<h3>Shorten a URL</h3>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="urlToShorten">Name</label>
|
||||
<input class="form-input" type="text" id="urlToShorten" placeholder="URL">
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-primary" id="shortenURL"
|
||||
onclick="shortURL('<%=user.token%>', document.getElementById('urlToShorten').value)"
|
||||
style="width:100%;">Shorten</button>
|
||||
</form>
|
||||
<h3 style="margin-top:5px;">Your Shortens</h3>
|
||||
<table class="table" style="margin:12px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Origin</th>
|
||||
<th scope="col">Shortened</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="shortensTableShortens">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="tab-content" style="margin:50px;">
|
||||
<h3>Create a Note</h3>
|
||||
<p>Comming Soon!</p>
|
||||
</div>
|
||||
<div class="tab-content" style="margin:50px;">
|
||||
<h3>Your Images</h3>
|
||||
<div id="emptyImages"></div>
|
||||
<div class="container" style="padding:12px;">
|
||||
<div class="container">
|
||||
<div id="typexImages" class="columns">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="pagination" style="justify-content: center;margin:12px;" id="typexImagePagination">
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content" style="margin:50px;">
|
||||
<div class="container">
|
||||
<button class="btn btn-primary"
|
||||
onclick="document.getElementById('modal-create-user').classList.add('active')">Create new
|
||||
user</button>
|
||||
<div class="modal" id="modal-create-user">
|
||||
<a href="#close" class="modal-overlay" aria-label="Close"></a>
|
||||
<div class="modal-container bg-dark">
|
||||
<div class="modal-header">
|
||||
<a href="#close" class="btn btn-clear float-right" aria-label="Close"></a>
|
||||
<div class="modal-title text-light h5">Create new user</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="content">
|
||||
<form>
|
||||
<label class="form-label" for="username">Username</label>
|
||||
<input class="form-input" type="text" id="username" name="username"
|
||||
placeholder="Username">
|
||||
<label class="form-label" for="password">Password</label>
|
||||
<input class="form-input" type="password" id="password" name="password"
|
||||
placeholder="Password">
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" id="administrator">
|
||||
<i class="form-icon"></i> Administrator
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" id="addUser" type="button">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<% for (let b = 0; b < users.length; b++) { let u = users[b]; let imgs = userImages[b]; %>
|
||||
<div class="column col-4">
|
||||
<div class="card bg-dark" style="border-color: #303742; margin:12px;">
|
||||
<div class="card-header">
|
||||
<div class="card-title h5"><%=u.username%> <button class="btn btn-error btn-sm"
|
||||
onclick="deleteSpecificUser('<%=u.id%>', '<%=u.username%>')">Delete</button>
|
||||
</div>
|
||||
<div class="card-subtitle text-gray">
|
||||
<%=u.administrator ? 'Administrator' : 'User'%>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<b>ID: </b> <%=u.id%><br>
|
||||
<b>Images: </b> <%=imgs%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%}%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="display:none" id="partenabled"><%=config.particles.enabled%></p>
|
||||
<p style="display:none" id="part"><%=JSON.stringify(config.particles.settings) %></p>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
|
||||
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
|
||||
integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
|
||||
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/sweetalert2@9/dist/sweetalert2.min.js"></script>
|
||||
<script src="http://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
|
||||
<script>if (document.getElementById('partenabled').innerText === "true") particlesJS("particles-js", JSON.parse(document.getElementById('part').innerText));</script>
|
||||
<script src="/public/js/dashboard.js"></script>
|
||||
<script>
|
||||
function whitespace(str) {
|
||||
return str === null || str.match(/^ *$/) !== null;
|
||||
}
|
||||
function showAlert(type, message) {
|
||||
if (type === 'error') {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Oops...',
|
||||
text: message,
|
||||
footer: 'Try again later...'
|
||||
})
|
||||
} else if (type === 'success') {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Success',
|
||||
text: message,
|
||||
footer: 'You did it!'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('updateImages').addEventListener('click', async () => {
|
||||
document.getElementById('typexImages').innerHTML = '';
|
||||
const res = await fetch('/api/images/user', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
try {
|
||||
const json = await res.json();
|
||||
if (json.error || json.code) return showAlert('error', json.error)
|
||||
else {
|
||||
json.forEach(image => {
|
||||
$('#typexImages').append(`
|
||||
<div class="card typex-image-actions typex-image-buttons"
|
||||
style="background-color: transparent;">
|
||||
<img class="card-img-top" src="${image.url}" onclick="window.location.href='${image.url}'"/>
|
||||
<input disabled type="button" class="typex-image-action btn btn-sm"
|
||||
value="${image.id}" />
|
||||
</div>
|
||||
`)
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
});
|
||||
|
||||
const copyText = (text) => {
|
||||
const el = document.createElement('textarea'); el.value = text; document.body.appendChild(el); el.select(); document.execCommand('copy'); document.body.removeChild(el);
|
||||
}
|
||||
|
||||
const deleteImage = (id, url) => {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: `You are proceeding to delete image (${id}), you will not be able to recover it!`,
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, delete it.'
|
||||
}).then(async (result) => {
|
||||
if (result.value) {
|
||||
const username = document.getElementById('usernameSave').value;
|
||||
const password = document.getElementById('passwordSave').value;
|
||||
if (whitespace(username)) return showAlert('error', 'Please input a username.')
|
||||
const res = await fetch('/api/user', {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username,
|
||||
password
|
||||
})
|
||||
});
|
||||
try {
|
||||
const json = await res.json();
|
||||
if (json.error || json.code) return showAlert('error', json.error)
|
||||
else {
|
||||
const res = await fetch('/api/images?image=' + id, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
try {
|
||||
const json = await res.json();
|
||||
if (json.error || json.code) return showAlert('error', json.error)
|
||||
else {
|
||||
Swal.fire(
|
||||
'Deleted!',
|
||||
`Deleted image (${id}) successfully.`,
|
||||
'success'
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
document.getElementById('saveUser').addEventListener('click', async () => {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: "You are proceeding to edit your user.",
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, save changes!'
|
||||
}).then(async (result) => {
|
||||
if (result.value) {
|
||||
const username = document.getElementById('usernameSave').value;
|
||||
const password = document.getElementById('passwordSave').value;
|
||||
if (whitespace(username)) return showAlert('error', 'Please input a username.')
|
||||
const res = await fetch('/api/user', {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username,
|
||||
password
|
||||
})
|
||||
});
|
||||
try {
|
||||
const json = await res.json();
|
||||
if (json.error || json.code) return showAlert('error', json.error)
|
||||
else {
|
||||
Swal.fire(
|
||||
'Saved Changes!',
|
||||
'Changes were saved successfully!',
|
||||
'success'
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('copyToken').addEventListener('click', async () => {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: "You are proceeding to copy your token, make sure NO ONE sees it.",
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, copy it!'
|
||||
}).then((result) => {
|
||||
if (result.value) {
|
||||
copyText("<%= user.token %>");
|
||||
Swal.fire(
|
||||
'Copied!',
|
||||
'Your API Token has been copied.',
|
||||
'success'
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
document.getElementById('regenToken').addEventListener('click', async () => {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: "You are proceeding to regenerate your token, remember all apps using your current one will stop working.",
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, regenerate it!'
|
||||
}).then(async (result) => {
|
||||
if (result.value) {
|
||||
const res = await fetch('/api/token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
try {
|
||||
const json = await res.json();
|
||||
if (json.error || json.code) return showAlert('error', json.error)
|
||||
else {
|
||||
Swal.fire(
|
||||
'Regenerated!',
|
||||
'Your API Token has been regenerated.',
|
||||
'success'
|
||||
);
|
||||
return window.location.href = '/'
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
async function createUser() {
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
if (whitespace(username)) return showAlert('error', 'Please input a username.')
|
||||
const res = await fetch('/api/user', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username,
|
||||
password,
|
||||
administrator: document.getElementById('administrator').checked
|
||||
})
|
||||
});
|
||||
try {
|
||||
const json = await res.json();
|
||||
if (json.error || json.code) return showAlert('error', json.error)
|
||||
else {
|
||||
$('#modal').modal('toggle');
|
||||
showAlert('success', `Created user ${json.username} (${json.id})`)
|
||||
return window.location.href = '/'
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
document.getElementById('createUser').addEventListener('click', async () => {
|
||||
if (document.getElementById('administrator').checked) {
|
||||
Swal.fire({
|
||||
title: 'Are you sure?',
|
||||
text: "You are proceeding to create a user with administrator permissions, they can do whatever they want!",
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Yes, create user!'
|
||||
}).then(async (result) => {
|
||||
if (result.value) {
|
||||
createUser()
|
||||
}
|
||||
});
|
||||
} else {
|
||||
createUser();
|
||||
}
|
||||
})
|
||||
document.getElementById('createUserButton').addEventListener("click", ()
|
||||
=> { document.getElementById("modal-create-user").classList.add("active") })
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
53
views/login.ejs
Normal file → Executable file
@@ -2,49 +2,38 @@
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<style>
|
||||
body,
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
<%- include('./partials/head') %>
|
||||
<link rel="icon" href="<%=config.meta.favicon%>" type="image/png">
|
||||
<title><%= config.meta.title %> - Login</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="particles-js"></div>
|
||||
<div class="container h-100 d-flex justify-content-center">
|
||||
<div class="jumbotron my-auto">
|
||||
<h1 class="display-4" style="text-align: center;">Login</h1>
|
||||
<form action="/login" method="post">
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username" placeholder="Your Username">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password"
|
||||
placeholder="Your Password">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" style="border-radius: 50px; width: 100%;">Login</button>
|
||||
</form>
|
||||
<div style="display:flex;align-items:center;justify-content:center;height:100%">
|
||||
<div class="card bg-dark" style="border-color: #303742;">
|
||||
<div class="card-header">
|
||||
<div class="card-title h5">Login</div>
|
||||
<div class="card-subtitle text-gray">Please enter in your username and password.</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="/login" method="post">
|
||||
<label class="form-label" for="username">Username</label>
|
||||
<input class="form-input" type="text" id="username" name="username" placeholder="Username">
|
||||
<label class="form-label" for="username">Password</label>
|
||||
<input class="form-input" type="password" id="password" name="password" placeholder="Password">
|
||||
<button style="margin-top:12px;width:100%" type="submit"
|
||||
class="btn btn-primary input-group-btn">Login</button>
|
||||
<% if (config.core.public) { %>
|
||||
<button style="margin-top:12px;width:100%" type="submit"
|
||||
class="btn btn-primary input-group-btn" href="/register">Register (Beta)</button>
|
||||
<% } %>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="display:none" id="part"><%=JSON.stringify(config.particles.settings) %></p>
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
|
||||
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
|
||||
integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
|
||||
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/sweetalert2@9/dist/sweetalert2.min.js"></script>
|
||||
<script src="http://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
|
||||
<script>if ("<%=config.particles.enabled%>" === "true") particlesJS("particles-js", JSON.parse(document.getElementById('part').innerText));</script>
|
||||
<script>
|
||||
if ("<%=failed%>" === 'true') {
|
||||
Swal.fire({
|
||||
|
||||
43
views/partials/head.ejs
Normal file → Executable file
@@ -1,5 +1,42 @@
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="/public/css/bootstrap-dark.min.css" />
|
||||
<link rel="stylesheet" href="/public/css/particles.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/public/css/spectre.css" />
|
||||
<link rel="stylesheet" href="/public/css/spectre-exp.css" />
|
||||
<link rel="stylesheet" href="/public/css/spectre-icons.css" />
|
||||
<link href="//cdn.jsdelivr.net/npm/@sweetalert2/theme-dark@3/dark.css" rel="stylesheet">
|
||||
<link rel="icon" href="<%=config.meta.favicon%>" type="image/png">
|
||||
|
||||
<script src="https://kit.fontawesome.com/583a38381a.js" crossorigin="anonymous"></script>
|
||||
<style>
|
||||
body,
|
||||
html {
|
||||
height: 100%;
|
||||
background: #21212c
|
||||
}
|
||||
|
||||
.tab-item>label:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-locator:nth-of-type(1):checked~.tab-block>.tab-item:nth-of-type(1)>label>a,
|
||||
.tab-locator:nth-of-type(2):checked~.tab-block>.tab-item:nth-of-type(2)>label>a,
|
||||
.tab-locator:nth-of-type(3):checked~.tab-block>.tab-item:nth-of-type(3)>label>a,
|
||||
.tab-locator:nth-of-type(4):checked~.tab-block>.tab-item:nth-of-type(4)>label>a,
|
||||
.tab-locator:nth-of-type(5):checked~.tab-block>.tab-item:nth-of-type(5)>label>a,
|
||||
.tab-locator:nth-of-type(6):checked~.tab-block>.tab-item:nth-of-type(6)>label>a {
|
||||
border-bottom-color: #5764c6;
|
||||
color: #5764c6;
|
||||
}
|
||||
|
||||
.tab-locator:nth-of-type(1):checked~.tabs>.tab-content:nth-of-type(1),
|
||||
.tab-locator:nth-of-type(2):checked~.tabs>.tab-content:nth-of-type(2),
|
||||
.tab-locator:nth-of-type(3):checked~.tabs>.tab-content:nth-of-type(3),
|
||||
.tab-locator:nth-of-type(4):checked~.tabs>.tab-content:nth-of-type(4),
|
||||
.tab-locator:nth-of-type(5):checked~.tabs>.tab-content:nth-of-type(5),
|
||||
.tab-locator:nth-of-type(6):checked~.tabs>.tab-content:nth-of-type(6) {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
5
views/partials/meta.ejs
Executable file
@@ -0,0 +1,5 @@
|
||||
<meta property="og:title" content="Zipline" />
|
||||
<meta name="theme-color" content="#09122B" />
|
||||
<meta property="og:description" content="Zipline is image/file uploading service.">
|
||||
<meta property="og:image"
|
||||
content="https://raw.githubusercontent.com/zipline-project/zipline/master/public/assets/typex_small_circle.png" />
|
||||