mirror of
https://github.com/diced/zipline.git
synced 2025-12-08 22:00:44 -08:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [dicedtomatoreal]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # ['lol']
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -4,6 +4,7 @@ tmp
|
||||
uploads
|
||||
config.json
|
||||
out
|
||||
public/assets/pd_logo.png
|
||||
.idea
|
||||
.vscode
|
||||
ssl/localhost.key
|
||||
ssl/localhost.crt
|
||||
5
.idea/.gitignore
generated
vendored
Normal file
5
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
6
.idea/misc.xml
generated
Normal file
6
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="ES6" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/typex.iml" filepath="$PROJECT_DIR$/.idea/typex.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
12
.idea/typex.iml
generated
Normal file
12
.idea/typex.iml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
325
README.md
325
README.md
@@ -4,28 +4,30 @@ A TypeScript based Image/File uploading server. Fast and Elegant.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
@@ -62,12 +64,12 @@ v13.13.0
|
||||
|
||||
### Common Databases
|
||||
|
||||
- PostgreSQL
|
||||
- CockroachDB
|
||||
- MySQL
|
||||
- MariaDB
|
||||
- Microsoft SQL Server
|
||||
- MongoDB (Coming soon!)
|
||||
- [PostgreSQL](https://www.postgresql.org/ "PostgresSQL")
|
||||
- [CockroachDB](https://www.cockroachlabs.com/ "Cockroach Labs")
|
||||
- [MySQL](https://www.mysql.com/ "MySQL")
|
||||
- [MariaDB](https://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)
|
||||
|
||||
@@ -115,19 +117,29 @@ Run the following command in order to get Microsoft SQL drivers
|
||||
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
|
||||
|
||||
You can get the source from the releases
|
||||
|
||||
```sh
|
||||
wget <RELEASE TAR BALL>
|
||||
tar -xvf <REALASE>
|
||||
cd <REALASE>
|
||||
npm i
|
||||
git clone https://github.com/dicedtomatoreal/typex
|
||||
cd typex
|
||||
tsc -p .
|
||||
npm start
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
@@ -143,6 +155,15 @@ Every single configuration option will be listed here
|
||||
| `upload.fileLength` | integer | how long the random id for a file should be |
|
||||
| `upload.tempDir` | string | temporary directory, files are stored here and then deleted. |
|
||||
| `upload.uploadDir` | string | upload directory (where all uploads are stored) |
|
||||
| `upload.route` | string | Route for uploads, default is /u, ex.`/u/hd27ua.png` |
|
||||
|
||||
#### 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
|
||||
|
||||
@@ -152,9 +173,10 @@ Every single configuration option will be listed here
|
||||
| --------------- | ------- | ------------------------------------------------------ |
|
||||
| `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)) |
|
||||
| `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 |
|
||||
|
||||
#### SSL Settings
|
||||
#### Site SSL Settings
|
||||
|
||||
**Config Property:** `site.ssl`
|
||||
|
||||
@@ -170,7 +192,6 @@ Every single configuration option will be listed here
|
||||
| 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
|
||||
|
||||
@@ -194,6 +215,12 @@ Every single configuration option will be listed here
|
||||
|
||||
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`
|
||||
@@ -205,168 +232,63 @@ Particles.JS, can be enabled and it's config can be changed willingly.
|
||||
| `meta.favicon` | string | has to be in /public/assets folder and should be formatted as `"/public/assets/<file name>"` |
|
||||
| `meta.title` | string | title of your server shows up like `<title> - Login` or `<title> - Dashboard` |
|
||||
|
||||
#### 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"
|
||||
"upload": {
|
||||
"fileLength": 6,
|
||||
"tempDir": "./temp",
|
||||
"uploadDir": "./uploads",
|
||||
"route": "/u"
|
||||
},
|
||||
"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
|
||||
}
|
||||
"shorten": {
|
||||
"idLength": 4,
|
||||
"route": "/s"
|
||||
},
|
||||
"user": {
|
||||
"tokenLength": 32
|
||||
},
|
||||
"site": {
|
||||
"protocol": "http",
|
||||
"returnProtocol": "https",
|
||||
"ssl": {
|
||||
"key": "./ssl/server.key",
|
||||
"cert": "./ssl/server.crt"
|
||||
},
|
||||
"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
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -385,3 +307,24 @@ 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>"}` |
|
||||
|
||||
396
package-lock.json
generated
396
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "typex",
|
||||
"version": "0.1.0",
|
||||
"version": "0.3.3",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -32,6 +32,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",
|
||||
@@ -128,6 +133,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 +175,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 +241,15 @@
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
|
||||
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
|
||||
},
|
||||
"bcrypt": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-4.0.1.tgz",
|
||||
"integrity": "sha512-hSIZHkUxIDS5zA2o00Kf2O5RfVbQ888n54xQoF/eIaquU4uaLxK8vhhBdktd0B3n2MjkcAWzv4mnhogykBKOUQ==",
|
||||
"requires": {
|
||||
"node-addon-api": "^2.0.0",
|
||||
"node-pre-gyp": "0.14.0"
|
||||
}
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||
@@ -268,6 +330,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 +422,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 +485,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 +540,16 @@
|
||||
"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="
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
@@ -473,6 +560,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 +726,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 +825,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 +865,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 +887,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",
|
||||
@@ -809,6 +975,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",
|
||||
@@ -847,11 +1030,118 @@
|
||||
"thenify-all": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"needle": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/needle/-/needle-2.4.1.tgz",
|
||||
"integrity": "sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g==",
|
||||
"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": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz",
|
||||
"integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
|
||||
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
|
||||
},
|
||||
"node-pre-gyp": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz",
|
||||
"integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==",
|
||||
"requires": {
|
||||
"detect-libc": "^1.0.2",
|
||||
"mkdirp": "^0.5.1",
|
||||
"needle": "^2.2.1",
|
||||
"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 +1168,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",
|
||||
@@ -1077,6 +1386,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 +1423,14 @@
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
|
||||
},
|
||||
"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",
|
||||
@@ -1186,6 +1514,11 @@
|
||||
"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=="
|
||||
},
|
||||
"split": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
|
||||
@@ -1233,6 +1566,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 +1579,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 +1709,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 +1809,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",
|
||||
|
||||
11
package.json
11
package.json
@@ -1,16 +1,20 @@
|
||||
{
|
||||
"name": "typex",
|
||||
"version": "0.3.0",
|
||||
"version": "0.5.0",
|
||||
"private": true,
|
||||
"scripts": {},
|
||||
"scripts": {
|
||||
"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/cookie-parser": "^1.4.2",
|
||||
"@types/express-session": "^1.17.0",
|
||||
"@types/mime": "^2.0.1",
|
||||
"@types/multer": "^1.4.3",
|
||||
"bcrypt": "^4.0.1",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"ejs": "^3.0.2",
|
||||
"express": "^4.17.1",
|
||||
@@ -18,9 +22,10 @@
|
||||
"http-status-codes": "^1.4.0",
|
||||
"mime": "^2.4.4",
|
||||
"multer": "^1.4.2",
|
||||
"node-fetch": "^2.6.0",
|
||||
"typeorm": "^0.2.24"
|
||||
},
|
||||
"devDependencies": {
|
||||
"pg": "^8.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
public/assets/typex-small.png
Normal file
BIN
public/assets/typex-small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
426
public/js/dashboard.js
Normal file
426
public/js/dashboard.js
Normal file
@@ -0,0 +1,426 @@
|
||||
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="col-sm-4">
|
||||
<div class="card m-2" data-toggle="modal" data-target="#typeximg${image.id}">
|
||||
<img class="card-img-top" src="${image.url}" alt="Image ${image.id}">
|
||||
<div class="modal fade" id="typeximg${image.id}" tabindex="-1" role="dialog" aria-labelledby="imagelabel${image.id}" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="imagelabel${image.id}">Currenting Managing Image ${image.id}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="row" style="width:100%;">
|
||||
<div class="col-sm-6">
|
||||
<button type="button" onclick="window.open('${image.url}', '_blank')" class="btn btn-primary"
|
||||
style="border-radius: 50px; width:100%;">View Image</button>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<button type="button" class="btn btn-danger" data-dismiss="modal"
|
||||
style="border-radius: 50px; width:100%;" onclick="deleteImage('${image.id}', '${image.url}')">Delete Image</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('updateImages').addEventListener('click', async () => {
|
||||
redoImageGrid('0', 'normal');
|
||||
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')">
|
||||
<span aria-hidden="true">«</span>
|
||||
<span class="sr-only">Previous</span>
|
||||
</a>
|
||||
</li>`);
|
||||
$('#typexImagePagination').append(`
|
||||
<li class="page-item">
|
||||
<select class="custom-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 class="page-link" aria-label="Next" onclick="redoImageGrid(null, 'next')">
|
||||
<span aria-hidden="true">»</span>
|
||||
<span class="sr-only">Next</span>
|
||||
</a>
|
||||
</li>`);
|
||||
$('#typexImagePagination').append(`
|
||||
<li class="page-item">
|
||||
<a class="page-link" aria-label="First" onclick="redoImageGrid(TypeX.pagedNumbers[TypeX.pagedNumbers.length-1], 'normal')">
|
||||
Last
|
||||
</a>
|
||||
</li>`);
|
||||
} 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 json = await res.json();
|
||||
if (json.error || json.code) return showAlert('error', json.error)
|
||||
else {
|
||||
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'
|
||||
);
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
document.getElementById('saveUser').addEventListener('click', () => {
|
||||
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/${TypeX.currentID}`, {
|
||||
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) {
|
||||
console.log(`/api/users/${id}`);
|
||||
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/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();
|
||||
}
|
||||
})
|
||||
@@ -1,15 +1,26 @@
|
||||
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 { randomId, getUser, getImage, findFile, getShorten, hashPassword } from '../util';
|
||||
import { createReadStream, createWriteStream, unlinkSync, existsSync, mkdirSync, readFileSync } from 'fs'
|
||||
import multer from 'multer'
|
||||
import { getExtension } from 'mime';
|
||||
import { User } from '../entities/User';
|
||||
import { sep } from 'path';
|
||||
import { cookiesForAPI } from '../middleware/cookiesForAPI';
|
||||
import Logger from '@ayanaware/logger';
|
||||
import { DiscordWebhook } from '../structures/DiscordWebhook';
|
||||
import { ImageUtil } from '../structures/ImageUtil';
|
||||
import { ShortenUtil } from '../structures/ShortenUtil';
|
||||
|
||||
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.upload.tempDir });
|
||||
|
||||
@Controller('api')
|
||||
@@ -34,92 +45,169 @@ export class APIController {
|
||||
source.on("end", function () {
|
||||
unlinkSync(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, `${config.site.returnProtocol}://${req.headers['host']}${config.upload.route}/${id}.${extension}`, 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(`${config.site.returnProtocol}://${req.headers['host']}${config.upload.route}/${id}.${extension}`)
|
||||
}
|
||||
|
||||
@Post('user')
|
||||
@Post('shorten')
|
||||
private async shorten(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 user = users[0];
|
||||
const id = randomId(config.shorten.idLength)
|
||||
const shrt = await getShorten(this.orm, id, req.body.url, `${config.site.returnProtocol}://${req.headers['host']}${config.shorten.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(`${config.site.returnProtocol}://${req.headers['host']}${config.shorten.route}/${id}`)
|
||||
}
|
||||
|
||||
@Get('users')
|
||||
@Middleware(cookiesForAPI)
|
||||
private async newUser(req: Request, res: Response) {
|
||||
private async getUsers(req: Request, res: Response) {
|
||||
if (!req.session.user.administrator) return res.status(FORBIDDEN).json({ code: FORBIDDEN, message: 'Unauthorized' });
|
||||
const data = req.body;
|
||||
try {
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
@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: data.password, administrator: data.administrator }))
|
||||
user = await this.orm.repos.user.save(new User().set({ username: data.username, password: hashPassword(data.password, config.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 })
|
||||
}
|
||||
}
|
||||
|
||||
@Patch('user')
|
||||
@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.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.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.user.tokenLength);
|
||||
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 })
|
||||
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(q) || req.session.user.id })
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
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')
|
||||
@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) })
|
||||
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(img) });
|
||||
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)}`);
|
||||
Logger.get('TypeX.Images.Delete').info(`Image ${image.url} (${image.id}) was deleted from ${config.upload.uploadDir}${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);
|
||||
}
|
||||
|
||||
public set(orm: ORMHandler) {
|
||||
this.orm = orm;
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import { OK, BAD_REQUEST, FORBIDDEN } from 'http-status-codes';
|
||||
import { Controller, Middleware, Get, Post, Put, Delete } from '@overnightjs/core';
|
||||
import { Controller, Middleware, Get, Post } 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 { findFile, checkPassword } from '../util';
|
||||
import { readFileSync } from 'fs'
|
||||
import multer from 'multer';
|
||||
import { cookies } from '../middleware/cookies';
|
||||
const upload = multer({ dest: 'temp/' });
|
||||
import Logger from '@ayanaware/logger';
|
||||
import { ShortenUtil } from '../structures/ShortenUtil';
|
||||
import { ImageUtil } from '../structures/ImageUtil';
|
||||
|
||||
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 {
|
||||
@@ -20,7 +25,11 @@ export class IndexController {
|
||||
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 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')
|
||||
@@ -31,6 +40,7 @@ export class IndexController {
|
||||
|
||||
@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');
|
||||
@@ -39,24 +49,31 @@ export class IndexController {
|
||||
@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.body.username === 'administrator' && req.body.password === config.administrator.password) {
|
||||
//@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 });
|
||||
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 })
|
||||
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('/')
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
@Get(`${config.shorten.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');
|
||||
return res.redirect(shorten.origin)
|
||||
}
|
||||
|
||||
public set(orm: ORMHandler) {
|
||||
|
||||
@@ -11,9 +11,13 @@ export class Image {
|
||||
@Column("bigint")
|
||||
user: number;
|
||||
|
||||
@Column("bigint", { default: 0 })
|
||||
views: number;
|
||||
|
||||
set(options: { url: string, user: number }) {
|
||||
this.url = options.url;
|
||||
this.user = options.user;
|
||||
this.views = 0;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
31
src/entities/Shorten.ts
Normal file
31
src/entities/Shorten.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Column, Entity, PrimaryColumn, 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
|
||||
|
||||
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;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
import { Column, Entity, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { randomId } from "../util";
|
||||
import config from '../../config.json';
|
||||
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 {
|
||||
|
||||
24
src/index.ts
24
src/index.ts
@@ -3,17 +3,32 @@ import {
|
||||
Repository,
|
||||
Connection,
|
||||
createConnection,
|
||||
ConnectionOptions,
|
||||
ConnectionOptions
|
||||
} from "typeorm";
|
||||
import { User } from "./entities/User";
|
||||
import { TypeXServer } from "./server";
|
||||
import config from "../config.json";
|
||||
import Logger from "@ayanaware/logger";
|
||||
import { Image } from "./entities/Image";
|
||||
import { findFile } from "./util";
|
||||
import { readFileSync } from 'fs';
|
||||
import { Shorten } from "./entities/Shorten";
|
||||
|
||||
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.upload?.route) {
|
||||
Logger.get('TypeX.Config').error(`Missing needed property on configuration: upload.route`)
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
export interface ORMRepos {
|
||||
user?: Repository<User>;
|
||||
image?: Repository<Image>;
|
||||
shorten?: Repository<Shorten>;
|
||||
}
|
||||
|
||||
export interface ORMHandler {
|
||||
@@ -21,6 +36,10 @@ export interface ORMHandler {
|
||||
connection: Connection;
|
||||
}
|
||||
|
||||
const pk = JSON.parse(readFileSync(findFile('package.json', process.cwd()), 'utf8'));
|
||||
|
||||
Logger.get('TypeX').info(`Starting TypeX ${pk.version}`);
|
||||
|
||||
(async () => {
|
||||
const connection = await createConnection(config.orm as ConnectionOptions);
|
||||
const orm: ORMHandler = {
|
||||
@@ -28,6 +47,7 @@ export interface ORMHandler {
|
||||
repos: {
|
||||
user: connection.getRepository(User),
|
||||
image: connection.getRepository(Image),
|
||||
shorten: connection.getRepository(Shorten)
|
||||
},
|
||||
};
|
||||
if (orm.connection.isConnected)
|
||||
|
||||
@@ -6,14 +6,14 @@ import { User } from "../entities/User";
|
||||
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 (Number(req.cookies.typex_user) === 0) {
|
||||
req.session.user = {
|
||||
id: 0,
|
||||
username: 'administrator',
|
||||
password: config.administrator.password,
|
||||
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;
|
||||
|
||||
@@ -7,11 +7,10 @@ import { User } from "../entities/User";
|
||||
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 = {
|
||||
if (Number(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 });
|
||||
|
||||
@@ -3,7 +3,6 @@ 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,10 +11,20 @@ import session from "express-session";
|
||||
import cookies from "cookie-parser";
|
||||
import { APIController } from "./controllers/APIController";
|
||||
import { IndexController } from "./controllers/IndexController";
|
||||
import { findFile } from "./util";
|
||||
import { ImageUtil } from "./structures/ImageUtil";
|
||||
|
||||
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 TypeXServer extends Server {
|
||||
constructor(orm: ORMHandler) {
|
||||
super();
|
||||
this.app.set("view engine", "ejs");
|
||||
this.app.use(
|
||||
session({
|
||||
secret: config.sessionSecret,
|
||||
@@ -23,12 +32,34 @@ export class TypeXServer extends Server {
|
||||
saveUninitialized: false,
|
||||
})
|
||||
);
|
||||
this.app.use(async (req, res, next) => {
|
||||
if (!req.url.startsWith(config.upload.route)) return next();
|
||||
const upload = await orm.repos.image.findOne({ url: `${config.site.returnProtocol}://${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.upload.route, express.static(config.upload.uploadDir));
|
||||
} 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.site.logRoutes) return next();
|
||||
if (req.url.startsWith(config.upload.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,12 +67,16 @@ 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") {
|
||||
try {
|
||||
|
||||
54
src/structures/DiscordWebhook.ts
Normal file
54
src/structures/DiscordWebhook.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import fetch from 'node-fetch';
|
||||
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 fetch(this.url)).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) {
|
||||
try {
|
||||
await (await fetch(this.url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user: config.discordWebhook.username,
|
||||
avatar_url: config.discordWebhook.avatarURL,
|
||||
content: `New image uploaded to <${image.origin}> by ${user.username} (${user.id}). <[View Image](${image.url})>`
|
||||
})
|
||||
}));
|
||||
} catch (e) {
|
||||
throw new Error(`Coulndn't send webhook: ${e.message}`)
|
||||
}
|
||||
}
|
||||
async sendShortenUpdate(user: User, shorten: Shorten, ex: Shortened, config: any) {
|
||||
try {
|
||||
await (await fetch(this.url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
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}>`
|
||||
})
|
||||
}));
|
||||
} catch (e) {
|
||||
throw new Error(`Coulndn't send webhook: ${e.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/structures/ImageUtil.ts
Normal file
34
src/structures/ImageUtil.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/structures/ShortenUtil.ts
Normal file
34
src/structures/ShortenUtil.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/util.ts
42
src/util.ts
@@ -1,8 +1,10 @@
|
||||
import { Request, Response } from 'express';
|
||||
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) {
|
||||
@@ -35,4 +37,40 @@ 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);
|
||||
}
|
||||
12
uploaders/UPLOADERS.md
Normal file
12
uploaders/UPLOADERS.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# TypeX Uploaders
|
||||
These uploaders are made with ShareX in mind, but can be adapted to other uploaders.
|
||||
Please make a Pull Request with other uploader configurations with the scheme of
|
||||
`uploader_type.json`.
|
||||
|
||||
## Images
|
||||
Images are sent to the `/api/upload/` endpoint with Multipart Form-Data. The payload key is `file`.
|
||||
|
||||
---
|
||||
|
||||
## URLs
|
||||
URLs are sent to the `/api/shorten/` endpoint with raw data. The payload key is `url`.
|
||||
15
uploaders/sharex_images.json
Normal file
15
uploaders/sharex_images.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"Version": "13.1.0",
|
||||
"Name": "<YOUR UPLOADER NAME>",
|
||||
"DestinationType": "ImageUploader",
|
||||
"RequestMethod": "POST",
|
||||
"RequestURL": "https://<YOUR DOMAIN>/api/upload",
|
||||
"Headers": {
|
||||
"authorization": "<YOUR TOKEN>"
|
||||
},
|
||||
"Body": "MultipartFormData",
|
||||
"Arguments": {
|
||||
"file": "@1"
|
||||
},
|
||||
"FileFormName": "file"
|
||||
}
|
||||
12
uploaders/sharex_urls.json
Normal file
12
uploaders/sharex_urls.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"Version": "13.1.0",
|
||||
"Name": "<YOUR UPLOADER NAME>",
|
||||
"DestinationType": "URLShortener",
|
||||
"RequestMethod": "POST",
|
||||
"RequestURL": "https://<YOUR DOMAIN>/api/shorten",
|
||||
"Headers": {
|
||||
"authorization": "<YOUR TOKEN>"
|
||||
},
|
||||
"Body": "JSON",
|
||||
"Data": "{\n \"url\": \"$input$\"\n}"
|
||||
}
|
||||
23
views/404.ejs
Normal file
23
views/404.ejs
Normal 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
Normal file
22
views/error.ejs
Normal 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>
|
||||
372
views/index.ejs
372
views/index.ejs
@@ -2,40 +2,23 @@
|
||||
<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">
|
||||
<div class="container my-auto">
|
||||
<ul class="nav nav-pills mb-3" id="main-view-tab-list" role="tablist" style="margin-top: 50px;">
|
||||
<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">
|
||||
<a style="border-radius: 50px;" class="nav-link" id="urls-tab" data-toggle="tab" href="#urls"
|
||||
role="tab" aria-controls="urls" aria-selected="false">URL Shortener</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
|
||||
@@ -56,9 +39,9 @@
|
||||
<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"
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="copyToken('<%=user.token%>')"
|
||||
style="border-radius: 50px;">Copy</button>
|
||||
<button type="button" class="btn btn-sm btn-danger" id="regenToken"
|
||||
<button type="button" class="btn btn-sm btn-danger" onclick="regenToken('<%=user.id%>')" id="regenToken"
|
||||
style="border-radius: 50px;">Regenerate</button>
|
||||
<h4 style="margin-top:12px">Update your Profile</h4>
|
||||
<form>
|
||||
@@ -70,55 +53,70 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label for="passwordSave">Password</label>
|
||||
<input value="<%= user.password %>" type="password" class="form-control" id="passwordSave"
|
||||
<input 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">
|
||||
<div class="tab-pane fade m-3" 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 class="row" id="typexImages">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<nav aria-label="TypeX Images" class="m-2">
|
||||
<ul class="pagination justify-content-center" id="typexImagePagination">
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
<div class="tab-pane fade m-3" id="urls" role="tabpanel" aria-labelledby="urls-tab">
|
||||
<h3>Shorten a URL</h3>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="urlToShorten">URL</label>
|
||||
<input type="text" class="form-control" id="urlToShorten"
|
||||
placeholder="URL">
|
||||
</div>
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-primary"
|
||||
id="shortenURL"
|
||||
onclick="shortURL('<%=user.token%>', document.getElementById('urlToShorten').value)"
|
||||
style="border-radius: 50px; width:100%;"
|
||||
>Shorten</button>
|
||||
</form>
|
||||
<h3>Your Shortens</h3>
|
||||
<p>Comming soon ™</p>
|
||||
</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"
|
||||
<button style="border-radius: 50px; margin-bottom: 10px;" type="button" class="btn btn-primary" data-toggle="modal"
|
||||
data-target="#modal">Create new
|
||||
User</button>
|
||||
<div class="row">
|
||||
<% for (let b = 0; b < users.length; b++) { let u = users[b]; let imgs = userImages[b]; %>
|
||||
<div class="col-sm-4">
|
||||
<div class="card m-2">
|
||||
<div class="card-body">
|
||||
<%if(u.administrator){%>
|
||||
<h5 class="card-title"><%=u.username%> <span class="badge badge-pill badge-danger">Admin</span></h5>
|
||||
<%}else{%>
|
||||
<h5 class="card-title"><%=u.username%> <span class="badge badge-pill badge-primary">User</span> <button class="badge badge-pill badge-danger" onclick="deleteSpecificUser('<%=u.id%>', '<%=u.username%>')" style="border:none;">Delete</button></h5>
|
||||
<%}%>
|
||||
<p class="card-text">
|
||||
<b>ID:</b> <%=u.id%><br>
|
||||
<b>Images:</b> <%=imgs%>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%}%>
|
||||
</div>
|
||||
<div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
@@ -171,8 +169,6 @@
|
||||
</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>
|
||||
@@ -183,260 +179,8 @@
|
||||
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>
|
||||
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();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<script src="/public/js/dashboard.js"></script>
|
||||
<script>TypeX.currentID = '<%=user.id%>'</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -2,20 +2,14 @@
|
||||
<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">
|
||||
<div class="jumbotron my-auto" style="background:none;">
|
||||
<h1 class="display-4" style="text-align: center;">Login</h1>
|
||||
<form action="/login" method="post">
|
||||
<div class="form-group">
|
||||
@@ -32,7 +26,6 @@
|
||||
</form>
|
||||
</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>
|
||||
@@ -43,8 +36,6 @@
|
||||
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({
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/public/css/bootstrap-dark.min.css" />
|
||||
<link rel="stylesheet" href="/public/css/particles.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">
|
||||
<style>
|
||||
body,
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
4
views/partials/meta.ejs
Normal file
4
views/partials/meta.ejs
Normal file
@@ -0,0 +1,4 @@
|
||||
<meta property="og:title" content="TypeX" />
|
||||
<meta name="theme-color" content="#09122B" />
|
||||
<meta property="og:description" content="TypeX is a Typescript based image/file uploading service.">
|
||||
<meta property="og:image" content="https://raw.githubusercontent.com/dicedtomatoreal/typex/master/public/assets/typex_small_circle.png" />
|
||||
Reference in New Issue
Block a user