Actually make file uploads work
This commit is contained in:
parent
6db8b1cd21
commit
a968cf8408
@ -1,9 +1,17 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (!document.getElementById('upload-form')) return;
|
||||
|
||||
const neverExpireCheckbox = document.getElementById('field-never_expire');
|
||||
const expireAfterDaysField = document.getElementById('field-expire_after_days');
|
||||
|
||||
const autogenUrlCheckbox = document.getElementById('field-autogen_url');
|
||||
const slugField = document.getElementById('field-slug');
|
||||
|
||||
neverExpireCheckbox.addEventListener('change', () => {
|
||||
// noinspection RedundantConditionalExpressionJS
|
||||
expireAfterDaysField.disabled = neverExpireCheckbox.checked ? true : false;
|
||||
expireAfterDaysField.disabled = neverExpireCheckbox.checked;
|
||||
});
|
||||
|
||||
autogenUrlCheckbox.addEventListener('change', () => {
|
||||
slugField.disabled = autogenUrlCheckbox.checked;
|
||||
});
|
||||
});
|
@ -18,8 +18,8 @@
|
||||
"@babel/preset-env": "^7.9.5",
|
||||
"@types/config": "^0.0.36",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/formidable": "^1.0.31",
|
||||
"@types/jest": "^25.2.1",
|
||||
"@types/multer": "^1.4.3",
|
||||
"@types/node": "^13.13.2",
|
||||
"babel-loader": "^8.1.0",
|
||||
"concurrently": "^5.1.0",
|
||||
@ -47,6 +47,6 @@
|
||||
"dependencies": {
|
||||
"config": "^3.3.1",
|
||||
"express": "^4.17.1",
|
||||
"multer": "^1.4.2"
|
||||
"formidable": "^1.2.2"
|
||||
}
|
||||
}
|
||||
|
31
src/App.ts
31
src/App.ts
@ -64,23 +64,15 @@ export default class App extends Application {
|
||||
const redisComponent = new RedisComponent();
|
||||
const mysqlComponent = new MysqlComponent();
|
||||
|
||||
// Session
|
||||
this.use(redisComponent);
|
||||
this.use(new SessionComponent(redisComponent));
|
||||
|
||||
// Static files
|
||||
this.use(new ServeStaticDirectoryComponent('public'));
|
||||
this.use(new ServeStaticDirectoryComponent('node_modules/feather-icons/dist', '/icons'));
|
||||
|
||||
// Utils
|
||||
this.use(new RedirectBackComponent());
|
||||
this.use(new FormHelperComponent());
|
||||
|
||||
const expressAppComponent = new ExpressAppComponent(this.port);
|
||||
this.use(expressAppComponent);
|
||||
this.use(new NunjucksComponent());
|
||||
this.use(new LogRequestsComponent());
|
||||
|
||||
// Static files
|
||||
this.use(new ServeStaticDirectoryComponent('public'));
|
||||
this.use(new ServeStaticDirectoryComponent('node_modules/feather-icons/dist', '/icons'));
|
||||
|
||||
// Maintenance
|
||||
this.use(new MaintenanceComponent(this, () => {
|
||||
return redisComponent.canServe() && mysqlComponent.canServe();
|
||||
@ -91,7 +83,9 @@ export default class App extends Application {
|
||||
this.use(mysqlComponent);
|
||||
this.use(new MailComponent());
|
||||
|
||||
// Auth
|
||||
// Session
|
||||
this.use(redisComponent);
|
||||
this.use(new SessionComponent(redisComponent));
|
||||
this.use(new AuthComponent(new class extends AuthGuard<MagicLink | AuthToken> {
|
||||
public async getProofForSession(session: Express.Session): Promise<any | null> {
|
||||
return await MagicLink.bySessionID(session.id, [MagicLinkActionType.LOGIN, MagicLinkActionType.REGISTER]);
|
||||
@ -100,13 +94,22 @@ export default class App extends Application {
|
||||
public async getProofForRequest(req: Request): Promise<MagicLink | AuthToken | null> {
|
||||
const authorization = req.header('Authorization');
|
||||
if (authorization) {
|
||||
return await AuthToken.getBySecret(authorization);
|
||||
const token = await AuthToken.getBySecret(authorization);
|
||||
if (token) {
|
||||
token.use();
|
||||
await token.save();
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
return super.getProofForRequest(req);
|
||||
}
|
||||
}));
|
||||
|
||||
// Utils
|
||||
this.use(new RedirectBackComponent());
|
||||
this.use(new FormHelperComponent());
|
||||
|
||||
// Middlewares
|
||||
this.use(new CsrfProtectionComponent());
|
||||
|
||||
|
@ -1,39 +1,74 @@
|
||||
import Controller from "wms-core/Controller";
|
||||
import {REQUIRE_AUTH_MIDDLEWARE} from "wms-core/auth/AuthComponent";
|
||||
import {Request, Response, Router} from "express";
|
||||
import {REQUIRE_AUTH_MIDDLEWARE, REQUIRE_REQUEST_AUTH_MIDDLEWARE} from "wms-core/auth/AuthComponent";
|
||||
import {Request, Response} from "express";
|
||||
import {BadRequestError, ForbiddenHttpError, NotFoundHttpError, ServerError} from "wms-core/HttpError";
|
||||
import FileModel from "../models/FileModel";
|
||||
import {cryptoRandomDictionary} from "wms-core/Utils";
|
||||
import config from "config";
|
||||
import * as fs from "fs";
|
||||
import multer from "multer";
|
||||
import AuthToken from "../models/AuthToken";
|
||||
import {IncomingForm} from "formidable";
|
||||
import {FILE_UPLOAD_MIDDLEWARE} from "wms-core/components/ExpressAppComponent";
|
||||
|
||||
const SLUG_DICTIONARY = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
|
||||
export default class FileController extends Controller {
|
||||
setupRequestParsingMiddlewares(router: Router) {
|
||||
router.use('/', FILE_UPLOAD_MIDDLEWARE);
|
||||
router.use('/:slug', FILE_UPLOAD_MIDDLEWARE);
|
||||
routes(): void {
|
||||
this.get('/files/upload', this.getFileUploader, 'file-upload', REQUIRE_AUTH_MIDDLEWARE);
|
||||
this.post('/files/post', this.postFileFrontend, 'post-file-frontend', REQUIRE_AUTH_MIDDLEWARE, FILE_UPLOAD_FORM_MIDDLEWARE);
|
||||
this.get('/files/:page([0-9]+)?', this.getFileManager, 'file-manager', REQUIRE_AUTH_MIDDLEWARE);
|
||||
this.get('/files/delete/:slug', this.deleteFile, 'delete-file-frontend', REQUIRE_AUTH_MIDDLEWARE);
|
||||
this.post('/gen-auth-token', this.postGenAuthToken, 'generate-token', REQUIRE_AUTH_MIDDLEWARE);
|
||||
this.get('/revoke-auth-token/:id', this.getRevokeAuthToken, 'revoke-token', REQUIRE_AUTH_MIDDLEWARE);
|
||||
|
||||
this.post('/', this.postFile, 'post-file', REQUIRE_REQUEST_AUTH_MIDDLEWARE, FILE_UPLOAD_FORM_MIDDLEWARE);
|
||||
this.delete('/delete/:slug', this.deleteFile, 'delete-file', REQUIRE_REQUEST_AUTH_MIDDLEWARE);
|
||||
this.get('/:slug', this.downloadFile, 'get-file');
|
||||
this.put('/:slug', this.putFile, 'put-file', REQUIRE_REQUEST_AUTH_MIDDLEWARE, FILE_UPLOAD_FORM_MIDDLEWARE);
|
||||
}
|
||||
|
||||
routes(): void {
|
||||
this.get('/files/:page?', this.getFileManager, 'file-manager', REQUIRE_AUTH_MIDDLEWARE);
|
||||
this.get('/files/delete/:slug', this.deleteFile, 'delete-file-frontend', REQUIRE_AUTH_MIDDLEWARE);
|
||||
|
||||
this.post('/', this.postFile, 'post-file', REQUIRE_AUTH_MIDDLEWARE);
|
||||
this.delete('/delete/:slug', this.deleteFile, 'delete-file', REQUIRE_AUTH_MIDDLEWARE);
|
||||
this.get('/:slug', this.downloadFile, 'get-file');
|
||||
this.put('/:slug', this.putFile, 'put-file', REQUIRE_AUTH_MIDDLEWARE);
|
||||
protected async getFileUploader(req: Request, res: Response): Promise<void> {
|
||||
res.render('file-upload', {
|
||||
max_upload_size: config.get<string>('max_upload_size'),
|
||||
auth_tokens: await AuthToken.getForUser(req.models.user!.id!),
|
||||
});
|
||||
}
|
||||
|
||||
protected async getFileManager(req: Request, res: Response): Promise<void> {
|
||||
res.render('file-manager', {
|
||||
files: await FileModel.paginate<FileModel>(req, 100),
|
||||
max_upload_size: config.get<string>('max_upload_size'),
|
||||
files: await FileModel.paginateForUser(req, 100, req.models.user!.id!),
|
||||
});
|
||||
}
|
||||
|
||||
protected async postGenAuthToken(req: Request, res: Response): Promise<void> {
|
||||
const authToken = new AuthToken({
|
||||
user_id: req.models.user!.id,
|
||||
ttl: req.body.ttl ? parseInt(req.body.ttl) : 365 * 24 * 3600,
|
||||
});
|
||||
await authToken.save();
|
||||
req.flash('success', 'Successfully created auth token.');
|
||||
res.redirectBack(Controller.route('file-upload'));
|
||||
}
|
||||
|
||||
protected async getRevokeAuthToken(req: Request, res: Response): Promise<void> {
|
||||
const id = req.params.id;
|
||||
if (!id) throw new BadRequestError('Cannot revoke token without an id.', 'Please provide an id.', req.url);
|
||||
|
||||
const authToken = await AuthToken.getById<AuthToken>(`${id}`);
|
||||
if (!authToken) throw new NotFoundHttpError('Auth token', req.url);
|
||||
if (!authToken.canDelete(req.models.user!.id!)) throw new ForbiddenHttpError('auth token', req.url);
|
||||
|
||||
await authToken.delete();
|
||||
|
||||
req.flash('success', 'Successfully deleted auth token.');
|
||||
res.redirectBack(Controller.route('file-upload'));
|
||||
}
|
||||
|
||||
protected async postFileFrontend(req: Request, res: Response): Promise<void> {
|
||||
await this.handleFileUpload(req.body.autogen_url === undefined && req.body.slug ? req.body.slug : await this.generateSlug(10), req, res);
|
||||
}
|
||||
|
||||
protected async downloadFile(req: Request, res: Response): Promise<void> {
|
||||
console.log('heeey');
|
||||
const file = await FileModel.getBySlug(req.params.slug);
|
||||
if (!file || file.shouldBeDeleted()) throw new NotFoundHttpError('File', req.url);
|
||||
|
||||
@ -59,11 +94,11 @@ export default class FileController extends Controller {
|
||||
|
||||
protected async handleFileUpload(slug: string, req: Request, res: Response): Promise<void> {
|
||||
// Check for file upload
|
||||
if (!req.file) {
|
||||
if (!req.files || !req.files['upload']) {
|
||||
throw new BadRequestError('No file received.', 'You must upload exactly one (1) file.', req.url);
|
||||
}
|
||||
|
||||
let upload = req.file;
|
||||
let upload = req.files['upload'];
|
||||
|
||||
// TTL
|
||||
let ttl = config.get<number>('default_file_ttl');
|
||||
@ -74,7 +109,7 @@ export default class FileController extends Controller {
|
||||
const file = new FileModel({
|
||||
user_id: req.models.user!.id,
|
||||
slug: slug,
|
||||
real_name: upload.originalname,
|
||||
real_name: upload.name,
|
||||
storage_type: 'local',
|
||||
storage_path: 'storage/uploads/' + slug,
|
||||
size: upload.size,
|
||||
@ -138,9 +173,10 @@ export default class FileController extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
const FILE_UPLOAD_MIDDLEWARE = multer({
|
||||
dest: 'storage/tmp',
|
||||
limits: {
|
||||
fileSize: config.get<number>('max_upload_size') * 1024 * 1024,
|
||||
},
|
||||
}).single('upload');
|
||||
|
||||
const FILE_UPLOAD_FORM_MIDDLEWARE = FILE_UPLOAD_MIDDLEWARE(() => {
|
||||
const form = new IncomingForm();
|
||||
form.uploadDir = 'storage/tmp';
|
||||
form.maxFileSize = config.get<number>('max_upload_size') * 1024 * 1024;
|
||||
return form;
|
||||
}, 'upload');
|
||||
|
@ -8,6 +8,7 @@ export default class CreateAuthTokensTable extends Migration {
|
||||
'user_id INT NOT NULL,' +
|
||||
'secret VARCHAR(64) UNIQUE NOT NULL,' +
|
||||
'created_at DATETIME NOT NULL DEFAULT NOW(),' +
|
||||
'used_at DATETIME NOT NULL DEFAULT NOW(),' +
|
||||
'ttl INT UNSIGNED NOT NULL,' +
|
||||
'PRIMARY KEY (id)' +
|
||||
')', connection);
|
||||
|
@ -3,6 +3,7 @@ import AuthProof from "wms-core/auth/AuthProof";
|
||||
import UserEmail from "wms-core/auth/models/UserEmail";
|
||||
import User from "wms-core/auth/models/User";
|
||||
import Validator from "wms-core/db/Validator";
|
||||
import {cryptoRandomDictionary} from "wms-core/Utils";
|
||||
|
||||
export default class AuthToken extends Model implements AuthProof {
|
||||
public static async getBySecret(secret: string): Promise<AuthToken | null> {
|
||||
@ -10,19 +11,41 @@ export default class AuthToken extends Model implements AuthProof {
|
||||
return models.length > 0 ? models[0] : null;
|
||||
}
|
||||
|
||||
public static async getForUser(user_id: number): Promise<AuthToken[]> {
|
||||
return await this.models<AuthToken>(this.select().where('user_id', user_id));
|
||||
}
|
||||
|
||||
protected readonly user_id!: number;
|
||||
protected readonly secret!: string;
|
||||
protected created_at?: Date;
|
||||
protected used_at?: Date;
|
||||
protected readonly ttl!: number;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
if (!this.secret) {
|
||||
this.secret = cryptoRandomDictionary(64, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected defineProperties() {
|
||||
this.defineProperty('user_id', new Validator().defined().exists(User, 'id'));
|
||||
this.defineProperty('secret', new Validator().defined().between(32, 64));
|
||||
this.defineProperty('created_at', new Validator());
|
||||
this.defineProperty('used_at', new Validator());
|
||||
this.defineProperty('ttl', new Validator().defined().min(1).max(5 * 365 * 24 * 3600)); // max 5 years
|
||||
}
|
||||
|
||||
public use() {
|
||||
this.used_at = new Date();
|
||||
}
|
||||
|
||||
public canDelete(user_id: number) {
|
||||
return this.user_id === user_id;
|
||||
}
|
||||
|
||||
public getExpirationDate(): Date {
|
||||
if (!this.created_at) return new Date();
|
||||
|
||||
|
@ -3,6 +3,7 @@ import Validator from "wms-core/db/Validator";
|
||||
import Controller from "wms-core/Controller";
|
||||
import config from "config";
|
||||
import User from "wms-core/auth/models/User";
|
||||
import {Request} from "express";
|
||||
|
||||
export default class FileModel extends Model {
|
||||
public static get table(): string {
|
||||
@ -14,6 +15,10 @@ export default class FileModel extends Model {
|
||||
return models.length > 0 ? models[0] : null;
|
||||
}
|
||||
|
||||
public static async paginateForUser(req: Request, perPage: number, user_id: number): Promise<FileModel[]> {
|
||||
return await this.paginate<FileModel>(req, perPage, this.select().where('user_id', user_id));
|
||||
}
|
||||
|
||||
public readonly user_id!: number;
|
||||
public readonly slug!: string;
|
||||
public readonly real_name!: string;
|
||||
|
@ -10,24 +10,6 @@
|
||||
<h1>File manager</h1>
|
||||
<p>You're their manager, please be nice with them.</p>
|
||||
|
||||
<div class="container">
|
||||
<section class="panel">
|
||||
<h2>Upload a file</h2>
|
||||
|
||||
<form action="{{ route('post-file') }}" method="POST" enctype="multipart/form-data">
|
||||
{{ macros.field(_locals, 'file', 'upload', '', 'Choose wisely', 'The maximum upload size is ' + max_upload_size + 'MiB', validation_attributes='required') }}
|
||||
|
||||
{{ macros.field(_locals, 'number', 'expire_after_days', '30', 'How many days to delete this file after', null, validation_attributes='max="1825"') }}
|
||||
|
||||
{{ macros.field(_locals, 'checkbox', 'never_expire', '', 'Never delete this file') }}
|
||||
|
||||
{{ macros.csrf(getCSRFToken) }}
|
||||
|
||||
<button type="submit"><i data-feather="upload"></i> Upload</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="panel">
|
||||
<h2>File list</h2>
|
||||
<table class="data-table">
|
||||
|
128
views/file-upload.njk
Normal file
128
views/file-upload.njk
Normal file
@ -0,0 +1,128 @@
|
||||
{% extends 'layouts/base.njk' %}
|
||||
|
||||
{% set title = 'ily.li - File manager' %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="/js/fm.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Upload files</h1>
|
||||
<p>(except illegal ones)</p>
|
||||
|
||||
<div class="container">
|
||||
<section class="panel">
|
||||
<h2>Upload a file</h2>
|
||||
|
||||
<form action="{{ route('post-file-frontend') }}" method="POST" enctype="multipart/form-data" id="upload-form">
|
||||
{{ macros.field(_locals, 'file', 'upload', '', 'Choose wisely', 'The maximum upload size is ' + max_upload_size + 'MiB', validation_attributes='required') }}
|
||||
|
||||
{{ macros.field(_locals, 'number', 'expire_after_days', '30', 'How many days to delete this file after', null, validation_attributes='max="1825"') }}
|
||||
{{ macros.field(_locals, 'checkbox', 'never_expire', '', 'Never delete this file') }}
|
||||
|
||||
{{ macros.field(_locals, 'text', 'slug', '', 'Custom url slug', 'Example: beautiful_image.jpg sets url to https://ily.li/beautiful_image.jpg', validation_attributes='disabled') }}
|
||||
{{ macros.field(_locals, 'checkbox', 'autogen_url', '', 'Generate url automatically', null, validation_attributes='checked') }}
|
||||
|
||||
{{ macros.csrf(getCSRFToken) }}
|
||||
|
||||
<button type="submit"><i data-feather="upload"></i> Upload</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h2>Setup a desktop utility</h2>
|
||||
<p>There may be a desktop client at some point. For now, if you're an advanced user, you can setup
|
||||
scripts/macros.</p>
|
||||
<p>
|
||||
To upload the file, you must:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Set the "Authorization" HTTP header to an auth token (generate one with the form below)</li>
|
||||
<li>Make a proper file upload request either with the method "POST" on / (auto-generates a short url) or
|
||||
"PUT" (choose the target url you want, alphanum)
|
||||
</li>
|
||||
</ul>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field name</th>
|
||||
<th>Description</th>
|
||||
<th>Optional?</th>
|
||||
<th>Example</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>upload</td>
|
||||
<td>The file field</td>
|
||||
<td>No</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>ttl</td>
|
||||
<td>How much time (in seconds) to keep the file</td>
|
||||
<td>Yes</td>
|
||||
<td>0 (never delete), 30 (delete after 30s)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Example with curl:</p>
|
||||
<pre>curl -X POST -H 'Accept: application/json' \
|
||||
-H "Authorization: very_secret_and_personal_token" \
|
||||
-F 'upload=@path/to/local/file' \
|
||||
https://ily.li/</pre>
|
||||
<pre>curl -X PUT -H 'Accept: application/json' \
|
||||
-H "Authorization: very_secret_and_personal_token" \
|
||||
-F 'upload=@path/to/local/file' \
|
||||
https://ily.li/my_very_important_file.png</pre>
|
||||
<pre>curl -X POST -H 'Accept: application/json' \
|
||||
-H "Authorization: very_secret_and_personal_token" \
|
||||
-F 'upload=@path/to/local/file' \
|
||||
-F 'ttl=30' \
|
||||
https://ily.li/</pre>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="panel">
|
||||
<h2>Auth tokens</h2>
|
||||
<form action="{{ route('generate-token') }}" method="POST">
|
||||
{{ macros.csrf(getCSRFToken) }}
|
||||
|
||||
<button type="submit"><i data-feather="plus"></i> Generate a new token</button>
|
||||
</form>
|
||||
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Secret</th>
|
||||
<th>Created at</th>
|
||||
<th>Last used at</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for token in auth_tokens %}
|
||||
<tr>
|
||||
<td>{{ token.id }}</td>
|
||||
<td>
|
||||
<div class="copyable-text">
|
||||
<div class="content">{{ token.secret }}</div>
|
||||
<button class="copy-button"><i data-feather="copy"></i></button>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ token.created_at.toISOString() }}</td>
|
||||
<td>{{ token.used_at.toISOString() }}</td>
|
||||
<td>
|
||||
<a href="{{ route('revoke-token', token.id) }}" class="button danger"><i
|
||||
data-feather="trash"></i> Revoke</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
{% endblock %}
|
@ -17,6 +17,7 @@
|
||||
<ul>
|
||||
<li><a href="{{ route('about') }}"><i data-feather="info"></i> About</a></li>
|
||||
{% if user %}
|
||||
<li><a href="{{ route('file-upload') }}"><i data-feather="upload"></i>File uploader</a></li>
|
||||
<li><a href="{{ route('file-manager') }}"><i data-feather="folder"></i>File manager</a></li>
|
||||
<li><a href="{{ route('logout') }}"><i data-feather="log-out"></i> Logout</a></li>
|
||||
{% else %}
|
||||
|
97
yarn.lock
97
yarn.lock
@ -1110,6 +1110,11 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/events@*":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.toot.party/@types%2fevents/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
|
||||
integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
|
||||
|
||||
"@types/express-serve-static-core@*":
|
||||
version "4.17.7"
|
||||
resolved "https://registry.toot.party/@types%2fexpress-serve-static-core/-/express-serve-static-core-4.17.7.tgz#dfe61f870eb549dc6d7e12050901847c7d7e915b"
|
||||
@ -1137,6 +1142,14 @@
|
||||
"@types/qs" "*"
|
||||
"@types/serve-static" "*"
|
||||
|
||||
"@types/formidable@^1.0.31":
|
||||
version "1.0.31"
|
||||
resolved "https://registry.toot.party/@types%2fformidable/-/formidable-1.0.31.tgz#274f9dc2d0a1a9ce1feef48c24ca0859e7ec947b"
|
||||
integrity sha512-dIhM5t8lRP0oWe2HF8MuPvdd1TpPTjhDMAqemcq6oIZQCBQTovhBAdTQ5L5veJB4pdQChadmHuxtB0YzqvfU3Q==
|
||||
dependencies:
|
||||
"@types/events" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/glob@^7.1.1":
|
||||
version "7.1.2"
|
||||
resolved "https://registry.toot.party/@types%2fglob/-/glob-7.1.2.tgz#06ca26521353a545d94a0adc74f38a59d232c987"
|
||||
@ -1195,13 +1208,6 @@
|
||||
resolved "https://registry.toot.party/@types%2fminimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
|
||||
|
||||
"@types/multer@^1.4.3":
|
||||
version "1.4.3"
|
||||
resolved "https://registry.toot.party/@types%2fmulter/-/multer-1.4.3.tgz#bdff74b334c38a8ee1de9fbedb5d1d3dbc377422"
|
||||
integrity sha512-tWsKbF5LYtXrJ7eOfI0aLBgEv9B7fnJe1JRXTj5+Z6EMfX0yHVsRFsNGnKyN8Bs0gtDv+JR37xAqsPnALyVTqg==
|
||||
dependencies:
|
||||
"@types/express" "*"
|
||||
|
||||
"@types/mysql@^2.15.10":
|
||||
version "2.15.13"
|
||||
resolved "https://registry.toot.party/@types%2fmysql/-/mysql-2.15.13.tgz#153dc2e2f8dffd39f7bba556c2679f14bdbecde1"
|
||||
@ -1591,11 +1597,6 @@ anymatch@^3.0.3, anymatch@~3.1.1:
|
||||
normalize-path "^3.0.0"
|
||||
picomatch "^2.0.4"
|
||||
|
||||
append-field@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.toot.party/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56"
|
||||
integrity sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=
|
||||
|
||||
aproba@^1.0.3, aproba@^1.1.1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.toot.party/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
|
||||
@ -2210,14 +2211,6 @@ builtin-status-codes@^3.0.0:
|
||||
resolved "https://registry.toot.party/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
|
||||
integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=
|
||||
|
||||
busboy@^0.2.11:
|
||||
version "0.2.14"
|
||||
resolved "https://registry.toot.party/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453"
|
||||
integrity sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=
|
||||
dependencies:
|
||||
dicer "0.2.5"
|
||||
readable-stream "1.1.x"
|
||||
|
||||
bytes@3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.toot.party/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
||||
@ -2623,7 +2616,7 @@ concat-map@0.0.1:
|
||||
resolved "https://registry.toot.party/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
||||
|
||||
concat-stream@^1.5.0, concat-stream@^1.5.2:
|
||||
concat-stream@^1.5.0:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.toot.party/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
|
||||
integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
|
||||
@ -3210,14 +3203,6 @@ detect-newline@^3.0.0:
|
||||
resolved "https://registry.toot.party/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
|
||||
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
|
||||
|
||||
dicer@0.2.5:
|
||||
version "0.2.5"
|
||||
resolved "https://registry.toot.party/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f"
|
||||
integrity sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=
|
||||
dependencies:
|
||||
readable-stream "1.1.x"
|
||||
streamsearch "0.1.2"
|
||||
|
||||
diff-sequences@^25.2.6:
|
||||
version "25.2.6"
|
||||
resolved "https://registry.toot.party/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd"
|
||||
@ -4066,6 +4051,11 @@ form-data@~2.3.2:
|
||||
combined-stream "^1.0.6"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
formidable@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.toot.party/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9"
|
||||
integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==
|
||||
|
||||
forwarded@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.toot.party/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
|
||||
@ -5190,11 +5180,6 @@ is-yarn-global@^0.3.0:
|
||||
resolved "https://registry.toot.party/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
|
||||
integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.toot.party/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
|
||||
|
||||
isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.toot.party/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
@ -6784,20 +6769,6 @@ ms@^2.1.1:
|
||||
resolved "https://registry.toot.party/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
multer@^1.4.2:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.toot.party/multer/-/multer-1.4.2.tgz#2f1f4d12dbaeeba74cb37e623f234bf4d3d2057a"
|
||||
integrity sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==
|
||||
dependencies:
|
||||
append-field "^1.0.0"
|
||||
busboy "^0.2.11"
|
||||
concat-stream "^1.5.2"
|
||||
mkdirp "^0.5.1"
|
||||
object-assign "^4.1.1"
|
||||
on-finished "^2.3.0"
|
||||
type-is "^1.6.4"
|
||||
xtend "^4.0.0"
|
||||
|
||||
mysql@^2.18.1:
|
||||
version "2.18.1"
|
||||
resolved "https://registry.toot.party/mysql/-/mysql-2.18.1.tgz#2254143855c5a8c73825e4522baf2ea021766717"
|
||||
@ -8049,16 +8020,6 @@ read-pkg@^5.2.0:
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readable-stream@1.1.x:
|
||||
version "1.1.14"
|
||||
resolved "https://registry.toot.party/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
|
||||
integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.1"
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
readable-stream@^3.1.1, readable-stream@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.toot.party/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
||||
@ -8931,11 +8892,6 @@ stream-shift@^1.0.0:
|
||||
resolved "https://registry.toot.party/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
|
||||
integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==
|
||||
|
||||
streamsearch@0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.toot.party/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
|
||||
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
|
||||
|
||||
strict-uri-encode@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.toot.party/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
|
||||
@ -9007,11 +8963,6 @@ string_decoder@^1.0.0, string_decoder@^1.1.1:
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
string_decoder@~0.10.x:
|
||||
version "0.10.31"
|
||||
resolved "https://registry.toot.party/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
|
||||
integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
|
||||
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.toot.party/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
@ -9480,7 +9431,7 @@ type-fest@^0.8.1:
|
||||
resolved "https://registry.toot.party/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
||||
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
||||
|
||||
type-is@^1.6.4, type-is@~1.6.17, type-is@~1.6.18:
|
||||
type-is@~1.6.17, type-is@~1.6.18:
|
||||
version "1.6.18"
|
||||
resolved "https://registry.toot.party/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
|
||||
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
|
||||
@ -9974,13 +9925,12 @@ widest-line@^3.1.0:
|
||||
string-width "^4.0.0"
|
||||
|
||||
wms-core@^0:
|
||||
version "0.7.12"
|
||||
resolved "https://registry.toot.party/wms-core/-/wms-core-0.7.12.tgz#9e198599df089962f636de7b28fa10d96f4167b7"
|
||||
integrity sha512-msKHjh+7QnPugpcxrLcSiVFBemtGkSQOG0fLFIRoTk5CORbZHLdghBeL7DGmSsfM1SV97om2XI9xSjz2A/Rj1Q==
|
||||
version "0.8.10"
|
||||
resolved "https://registry.toot.party/wms-core/-/wms-core-0.8.10.tgz#8db64926f9bd9eaa61556ad98dcd215361b16950"
|
||||
integrity sha512-1GJkpOX/efoDGK9JOwHnBkD55Q6dEKTQsdHpgMmF3g0h7XxJFAAsZTv0tOyfYHIEpJo5mNdR/+mzdZTPsgtAdw==
|
||||
dependencies:
|
||||
"@types/express" "^4.17.6"
|
||||
"@types/express-session" "^1.17.0"
|
||||
"@types/multer" "^1.4.3"
|
||||
"@types/mysql" "^2.15.10"
|
||||
"@types/nodemailer" "^6.4.0"
|
||||
"@types/nunjucks" "^3.1.3"
|
||||
@ -9994,6 +9944,7 @@ wms-core@^0:
|
||||
cookie-parser "^1.4.5"
|
||||
express "^4.17.1"
|
||||
express-session "^1.17.1"
|
||||
formidable "^1.2.2"
|
||||
geoip-lite "^1.4.2"
|
||||
mjml "^4.6.2"
|
||||
mysql "^2.18.1"
|
||||
|
Loading…
Reference in New Issue
Block a user