Compare commits

..

51 Commits

Author SHA1 Message Date
dicedtomatoreal
91b2b9b2de versioning 2020-05-28 09:02:14 -07:00
dicedtomatoreal
4fe9370ce2 shortening api added 2020-05-28 09:01:34 -07:00
dicedtomatoreal
db02b1ddb3 frontend uses new api routes and backend fixed 2020-05-28 08:59:27 -07:00
dicedtomatoreal
73267e3e90 typo in user api 2020-05-28 08:33:49 -07:00
dicedtomatoreal
93edf6ec19 image api refactor 2020-05-28 08:30:52 -07:00
dicedtomatoreal
6f2b0bbd0d users api refactor 2020-05-28 08:09:50 -07:00
dicedtomatoreal
79b5110e21 reset password api thing 2020-05-27 21:40:52 -07:00
dicedtomatoreal
cf5df1150d update version cause i didnt do it for a few commits 2020-05-27 21:34:45 -07:00
dicedtomato
f4793e7f30 Delete main.yml 2020-05-27 21:30:46 -07:00
dicedtomato
76acc54e84 Merge pull request #9 from KryskZ09/master
 Add uploader templates :
2020-05-27 21:28:35 -07:00
Clayton Jensen
044c9adc0a Add uploader templates 2020-05-27 23:26:14 -05:00
dicedtomato
5fb9d80cfd Update main.yml 2020-05-27 21:26:08 -07:00
dicedtomatoreal
930056b323 pp 2020-05-27 21:24:41 -07:00
dicedtomatoreal
9acc2d8768 idk 2020-05-27 21:22:47 -07:00
dicedtomato
1f32cadff0 Merge pull request #8 from TacticalTechJay/patch-3
Update README.md
2020-05-27 21:21:10 -07:00
dicedtomatoreal
85d4cd6ae1 remove style on all ejs and add to head.ejs 2020-05-27 21:02:55 -07:00
dicedtomato
1ea6ffffec ADD DISCORD WORKFLOW 2020-05-27 21:00:07 -07:00
dicedtomatoreal
132c55397e fix invalid login, and pagination! 2020-05-27 20:25:41 -07:00
dicedtomatoreal
c9fa30cd65 maybe this fixes it 2020-05-26 18:25:55 -07:00
dicedtomatoreal
3b4e3da1cc cool dashboard thing :OOOO 2020-05-26 17:52:34 -07:00
TacticalCoderJay
90df30de59 Update README.md
Added more hyperlinks and stuff.
2020-05-25 11:39:24 -07:00
dicedtomatoreal
171b7111bb meta and login page fixes 2020-05-21 10:14:51 -07:00
dicedtomatoreal
20dbb14903 readme 2020-05-17 07:45:21 -07:00
dicedtomatoreal
07aad78993 url shortening 2020-05-17 07:27:34 -07:00
dicedtomatoreal
0e03736923 fix webhooks 2020-05-16 08:33:57 -07:00
dicedtomatoreal
cb8bde63f0 new ui update 2020-05-16 08:33:11 -07:00
dicedtomatoreal
5494f52ddf dont show hash on password input bruh 2020-05-13 16:17:50 -07:00
dicedtomatoreal
29a94ec843 added encryption 2020-05-13 16:16:24 -07:00
dicedtomatoreal
c37e933cc9 add ogp 2020-05-12 20:41:09 -07:00
dicedtomatoreal
01bf445644 fix administrator 2020-05-04 22:01:25 -07:00
dicedtomatoreal
20c3bf9910 cool 2020-05-04 20:00:13 -07:00
dicedtomato
5c2ee55994 Merge pull request #3 from TacticalTechJay/patch-2
booooooooo
2020-04-27 13:50:47 -07:00
dicedtomato
8790d9b2c0 Merge pull request #4 from Puyodead1/master
[View Image button] Open images in new tab
2020-04-27 13:50:03 -07:00
Puyodead1
46fa7a84e3 View Image button opens image in new tab 2020-04-27 14:34:31 -04:00
dicedtomatoreal
bacb4d5d8d versioning 2020-04-27 10:03:03 -07:00
dicedtomatoreal
28e9d1194e add returnProtocol and imageutil 2020-04-27 10:01:28 -07:00
dicedtomatoreal
811ee2a87d add returnProtocol and imageutil 2020-04-27 10:00:09 -07:00
TacticalCoderJay
281580b879 Update README.md
I unfricked the fricked
2020-04-27 09:41:31 -07:00
dicedtomato
aaeac1b7e3 Merge pull request #2 from TacticalTechJay/patch-1
Adding clicky bois
2020-04-27 09:36:49 -07:00
TacticalCoderJay
f9d17e9765 Update README.md 2020-04-27 09:34:46 -07:00
TacticalCoderJay
409207ab7d Update README.md 2020-04-27 09:30:44 -07:00
dicedtomatoreal
ba33756416 git aupdate image click action with modal 2020-04-27 08:52:13 -07:00
dicedtomatoreal
a6cf2a5c46 check for needed routes, added logs for indexcontroller 2020-04-26 19:57:37 -07:00
dicedtomatoreal
9d2eb09789 update version 2020-04-26 17:54:31 -07:00
dicedtomatoreal
346ed4d17a update readme 2020-04-26 17:54:18 -07:00
dicedtomatoreal
d66d50ef73 git add .add logging for everything 2020-04-26 17:53:15 -07:00
dicedtomatoreal
4be174e325 test 2020-04-26 17:28:55 -07:00
dicedtomato
9023978d0f whats this 2020-04-26 15:11:17 -07:00
dicedtomatoreal
8ddd678864 updated readme with new stuff 2020-04-26 14:59:49 -07:00
dicedtomatoreal
1c18828e1b config finding, 404 and 500 pages added 2020-04-26 14:41:55 -07:00
dicedtomatoreal
c2553c853f find file added for config search 2020-04-26 14:34:42 -07:00
34 changed files with 1612 additions and 603 deletions

12
.github/FUNDING.yml vendored Normal file
View 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
View File

@@ -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
View File

@@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

6
.idea/misc.xml generated Normal file
View 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
View 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
View 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
View 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
View File

@@ -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
View File

@@ -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",

View File

@@ -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"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

426
public/js/dashboard.js Normal file
View 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">&times;</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">&laquo;</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">&raquo;</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();
}
})

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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
View 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;
}
}

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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;

View File

@@ -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 });

View File

@@ -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 {

View 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}`)
}
}
}

View 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
}
}
}

View 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
}
}
}

View File

@@ -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
View 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`.

View 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"
}

View 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
View 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
View 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>

View File

@@ -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 &trade;</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>

View File

@@ -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({

View File

@@ -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
View 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" />