tspace-nfs
Version:
tspace-nfs is a Network File System (NFS) and provides both server and client capabilities for accessing files over a network.
1,047 lines • 67.9 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.NfsServer = void 0;
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
const xml_1 = __importDefault(require("xml"));
const bcrypt_1 = __importDefault(require("bcrypt"));
const node_cron_1 = __importDefault(require("node-cron"));
const tspace_utils_1 = require("tspace-utils");
const html_minifier_terser_1 = require("html-minifier-terser");
const tspace_spear_1 = require("tspace-spear");
const queue_1 = __importDefault(require("./queue"));
const default_html_1 = __importDefault(require("./default-html"));
/**
* The 'NfsServer' class is a created the server for nfs
*
* @example
* import { NfsServer } from "tspace-nfs";
*
* new NfsServer()
* .listen(8000 , ({ port }) => console.log(`Server is running on port http://localhost:${port}`))
*/
class NfsServer {
constructor() {
this._queue = new queue_1.default(3);
this._fileExpired = 60 * 60;
this._rootFolder = 'nfs';
this._cluster = false;
this._jwtExipred = 60 * 60;
this._jwtSecret = `<secret@${+new Date()}:${Math.floor(Math.random() * 9999)}>`;
this._progress = false;
this._debug = false;
this._trash = '@trash';
this._backup = 30;
this._default = ({ res }) => __awaiter(this, void 0, void 0, function* () {
return res.html(this._html == null ? default_html_1.default : String(this._html));
});
this._benchmark = () => {
return 'benchmark in nfs server';
};
this._media = ({ req, res, query, params }) => __awaiter(this, void 0, void 0, function* () {
var _a;
try {
const { AccessKey, Expires, Signature, Download } = query;
const bucket = params.bucket;
if ([
AccessKey, Expires, Signature, Download, bucket
].some(v => v === '' || v == null)) {
res.writeHead(400, { 'Content-Type': 'text/xml' });
const error = {
Error: [
{ Code: 'Bad request' },
{ Message: 'The request was invalid' },
{ Resource: req.url },
{ RequestKey: query === null || query === void 0 ? void 0 : query.key }
]
};
return res.end((0, xml_1.default)([error], { declaration: true }));
}
const path = String(params['*']).replace(/^\/+/, '').replace(/\.{2}(?!\.)/g, "");
const combined = `@{${path}-${bucket}-${AccessKey}-${Expires}-${Download}}`;
const compare = bcrypt_1.default.compareSync(combined, Buffer.from(Signature, 'base64').toString('utf-8'));
const expired = Number.isNaN(+Expires) ? true : new Date(+Expires) < new Date();
if (!compare || expired) {
res.writeHead(400, { 'Content-Type': 'text/xml' });
const error = {
Error: [
{ Code: expired ? 'Expired' : 'AccessDenied' },
{ Message: expired ? 'Request has expired' : 'The signature is not correct' },
{ Resource: req.url },
{ RequestKey: query.key }
]
};
return res.end((0, xml_1.default)([error], { declaration: true }));
}
const { stream, header, set } = yield this._makeStream({
bucket: bucket,
filePath: String(path),
range: (_a = req.headers) === null || _a === void 0 ? void 0 : _a.range,
download: Download === Buffer.from(`${Expires}@true`).toString('base64').replace(/[=|?|&]+$/g, '')
});
if (stream == null || header == null) {
res.writeHead(404, { 'Content-Type': 'text/xml' });
const error = {
Error: [
{ Code: 'Not found' },
{ Message: 'The file does not exist in our records' },
{ Resource: req.url },
{ RequestKey: query.key }
]
};
return res.end((0, xml_1.default)([error], { declaration: true }));
}
set(res);
return stream.pipe(res);
}
catch (err) {
res.writeHead(400, { 'Content-Type': 'text/xml' });
const error = {
Error: [
{ Code: 'AccessDenied' },
{ Message: err.message },
{ Resource: req.url },
{ RequestKey: query.key }
]
};
return res.end((0, xml_1.default)([error], { declaration: true }));
}
});
this._apiFile = ({ req, res, body }) => __awaiter(this, void 0, void 0, function* () {
try {
const { bucket, token } = req;
let { path, download, expired } = body;
const fileName = `${path}`.replace(/^\/+/, '');
const directory = this._normalizeDirectory({ bucket, folder: null });
const fullPath = this._normalizePath({ directory, path: String(path), full: true });
if (!(yield this._fileExists(fullPath))) {
if (this._debug) {
console.log({
fullPath,
path,
download,
expired
});
}
return res.status(404).json({
message: `No such directory or file, '${fileName}'`
});
}
const key = String(token);
const expires = new tspace_utils_1.Time().addSeconds(expired == null || Number.isNaN(Number(expired)) ? this._fileExpired : Number(expired)).toTimeStamp();
const downloaded = `${Buffer.from(`${expires}@${download}`).toString('base64').replace(/[=|?|&]+$/g, '')}`;
const combined = `@{${path}-${bucket}-${key}-${expires}-${downloaded}}`;
const signature = Buffer.from(bcrypt_1.default.hashSync(combined, 1)).toString('base64');
return res.ok({
endpoint: [
`${bucket}/${fileName}?AccessKey=${key}`,
`Expires=${expires}`,
`Download=${downloaded}`,
`Signature=${signature}`
].join('&')
});
}
catch (err) {
if (this._debug) {
console.log(err);
}
return res.status(500)
.json({
message: err.message
});
}
});
this._apiBase64 = ({ req, res, body }) => __awaiter(this, void 0, void 0, function* () {
try {
const { bucket } = req;
const { path: filename } = body;
const directory = this._normalizeDirectory({ bucket, folder: null });
const path = this._normalizePath({ directory, path: String(filename), full: true });
if (!(yield this._fileExists(path))) {
return res.status(404).json({
message: `no such file or directory, '${filename}'`
});
}
return res.json({
base64: fs_1.default.readFileSync(path, 'base64')
});
}
catch (err) {
if (this._debug) {
console.log(err);
}
return res.status(500).json({
message: err.message
});
}
});
this._apiStream = ({ req, res, body }) => __awaiter(this, void 0, void 0, function* () {
try {
const { bucket } = req;
const { path: filename, range } = body;
const directory = this._normalizeDirectory({ bucket, folder: null });
const fullPath = this._normalizePath({ directory, path: String(filename), full: true });
if (!(yield this._fileExists(fullPath))) {
return res.status(404).json({
message: `no such file or directory, '${filename}'`
});
}
const stat = fs_1.default.statSync(fullPath);
const fileSize = stat.size;
if (range) {
const parts = String(range).replace(/bytes=/, "").split("-");
const start = parseInt(parts[0], 10);
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
const chunksize = (end - start) + 1;
const file = fs_1.default.createReadStream(fullPath, { start, end });
const head = {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunksize,
'Content-Type': 'video/mp4',
};
res.writeHead(206, head);
return file.pipe(res);
}
const head = {
'Content-Length': fileSize,
'Content-Type': 'video/mp4',
};
res.writeHead(200, head);
return fs_1.default.createReadStream(fullPath).pipe(res);
}
catch (err) {
if (this._debug) {
console.log(err);
}
throw err;
}
});
this._apiStorage = ({ req, res, body }) => __awaiter(this, void 0, void 0, function* () {
try {
const { bucket } = req;
let { folder } = body;
if (folder != null) {
folder = this._normalizeFolder(String(folder));
}
const directory = this._normalizeDirectory({ bucket, folder });
if (!(yield this._fileExists(directory))) {
return res.status(404).json({
message: `No such directory or folder, '${folder}'`
});
}
const fileDirectories = yield this._files(directory, { ignore: this._trash });
const storage = fileDirectories.map((name) => {
const stat = fs_1.default.statSync(name);
return {
name: path_1.default.relative(directory, name).replace(/\\/g, '/'),
size: Number((stat.size / (1024 * 1024)))
};
});
return res.ok({
storage
});
}
catch (err) {
if (this._debug) {
console.log(err);
}
return res.status(500).json({
message: err.message
});
}
});
this._apiFolders = ({ req, res }) => __awaiter(this, void 0, void 0, function* () {
try {
const { bucket } = req;
const directory = this._normalizeDirectory({ bucket, folder: null });
const folders = fs_1.default.readdirSync(directory);
return res.ok({
folders
});
}
catch (err) {
if (this._debug) {
console.log(err);
}
return res.status(500).json({
message: err.message
});
}
});
this._apiUpload = ({ req, res, files, body }) => __awaiter(this, void 0, void 0, function* () {
try {
const { bucket } = req;
if (!Array.isArray(files === null || files === void 0 ? void 0 : files.file)) {
return res.status(400).json({
message: 'The file is required.'
});
}
const file = files === null || files === void 0 ? void 0 : files.file[0];
if (file == null) {
return res.status(400).json({
message: 'The file is required.'
});
}
let { folder } = body;
if (folder != null) {
folder = this._normalizeFolder(String(folder));
}
const directory = this._normalizeDirectory({ bucket, folder });
if (!(yield this._fileExists(directory))) {
if (this._debug) {
console.log({ directory, bucket, folder });
}
fs_1.default.mkdirSync(directory, {
recursive: true
});
}
const writeFile = (file, to) => {
return new Promise((resolve, reject) => {
fs_1.default.createReadStream(file)
.pipe(fs_1.default.createWriteStream(to))
.on('finish', () => {
// remove temporary from chunked by nfs-client
this._remove(to);
// remove temporary from server
this._remove(file, { delayMs: 0 });
return resolve(null);
})
.on('error', (err) => reject(err));
return;
});
};
yield writeFile(file.tempFilePath, this._normalizePath({ directory, path: file.name, full: true }));
return res.ok({
path: this._normalizePath({ directory: folder, path: file.name }),
name: file.name,
size: file.size
});
}
catch (err) {
if (this._debug) {
console.log(err);
}
throw err;
}
});
this._apiMerge = ({ req, res, body }) => __awaiter(this, void 0, void 0, function* () {
try {
const { bucket } = req;
let { folder, name, paths, totalSize } = body;
if (folder != null) {
folder = this._normalizeFolder(String(folder));
}
const directory = this._normalizeDirectory({ bucket, folder });
if (!(yield this._fileExists(directory))) {
fs_1.default.mkdirSync(directory, {
recursive: true
});
}
const writeFile = (to) => __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
const writeStream = fs_1.default.createWriteStream(to, { flags: 'a' });
writeStream.on('error', (err) => {
return reject(err);
});
let processedSize = 0;
const next = (index = 0) => {
if (index >= paths.length) {
writeStream.end();
writeStream.close();
return resolve(null);
}
const partPath = this._normalizePath({
directory,
path: paths[index],
full: true
});
const readStream = fs_1.default.createReadStream(partPath, {
highWaterMark: 1024 * 1024 * 100
});
if (this._progress) {
readStream.on('data', (chunk) => {
processedSize += chunk.length;
const progress = ((processedSize / totalSize) * 100).toFixed(2);
console.log(`The file '${path_1.default.basename(to)}' in progress: ${progress}%`);
});
}
readStream.on('error', (err) => {
return reject(err);
});
readStream.on('end', () => {
this._remove(partPath, { delayMs: 0 });
next(index + 1);
});
readStream.pipe(writeStream, { end: false });
};
next();
});
});
const to = this._normalizePath({ directory, path: name, full: true });
yield writeFile(to);
return res.ok({
path: this._normalizePath({ directory: folder, path: name }),
name: name,
size: fs_1.default.statSync(to).size
});
}
catch (err) {
if (this._debug) {
console.log(err);
}
throw err;
}
});
this._apiUploadBase64 = ({ req, res, body }) => __awaiter(this, void 0, void 0, function* () {
try {
const { bucket } = req;
let { folder, base64, name } = body;
if (folder != null) {
folder = this._normalizeFolder(String(folder));
}
if (base64 === '' || base64 == null) {
return res.status(400).json({
message: 'The base64 is required.'
});
}
if (name === '' || name == null) {
return res.status(400).json({
message: 'The name is required.'
});
}
const directory = this._normalizeDirectory({ bucket, folder });
if (!(yield this._fileExists(directory))) {
fs_1.default.mkdirSync(directory, {
recursive: true
});
}
const writeFile = (base64, to) => {
return fs_1.default.writeFileSync(to, String(base64), 'base64');
};
const to = path_1.default.join(path_1.default.resolve(), `${directory}/${name}`);
writeFile(String(base64), to);
return res.ok({
path: folder ? `${folder}/${name}` : name,
name: name,
size: fs_1.default.statSync(to).size
});
}
catch (err) {
if (this._debug) {
console.log(err);
}
throw err;
}
});
this._apiRemove = ({ req, res, body }) => __awaiter(this, void 0, void 0, function* () {
try {
const { bucket } = req;
const { path: p } = body;
const path = `${p}`.replace(/^\/+/, '');
const directory = this._normalizeDirectory({ bucket, folder: null });
const fullPath = this._normalizePath({ directory, path: path, full: true });
if (!(yield this._fileExists(fullPath))) {
return res.status(404)
.json({
message: `No such directory or file, '${path}'`
});
}
this._queue.add(() => __awaiter(this, void 0, void 0, function* () { return yield this._trashed({ path, bucket }); }));
return res.ok();
}
catch (err) {
if (this._debug) {
console.log(err);
}
return res.status(500).json({
message: err.message
});
}
});
this._apiConnect = ({ res, body }) => __awaiter(this, void 0, void 0, function* () {
const { token, secret, bucket } = body;
if (this._credentials != null) {
const credentials = yield this._credentials({
token: String(token),
secret: String(secret),
bucket: String(bucket)
});
if (!credentials) {
return res.status(401).json({
message: 'Invalid credentials. Please check the your credentials'
});
}
}
const directory = path_1.default.join(path_1.default.resolve(), this._normalizeDirectory({ bucket: String(bucket) }));
if (!(yield this._fileExists(directory))) {
fs_1.default.mkdirSync(directory, {
recursive: true
});
}
return res.json({
accessToken: jsonwebtoken_1.default.sign({
data: {
issuer: 'nfs-server',
sub: {
bucket,
token
}
}
}, this._jwtSecret, {
expiresIn: this._jwtExipred,
algorithm: 'HS256'
})
});
});
this._studio = ({ res, cookies }) => __awaiter(this, void 0, void 0, function* () {
const auth = cookies['auth.session'];
if (!auth || auth == null) {
const html = fs_1.default.readFileSync(path_1.default.join(__dirname, 'studio', 'login.html'), 'utf8');
return res.html(html);
}
const html = fs_1.default.readFileSync(path_1.default.join(__dirname, 'studio', 'index.html'), 'utf8');
const minifiedHtml = yield (0, html_minifier_terser_1.minify)(html, {
collapseWhitespace: true,
removeComments: true,
minifyCSS: true,
minifyJS: true
});
return res.html(minifiedHtml);
});
this._studioStorage = ({ req, res }) => __awaiter(this, void 0, void 0, function* () {
var _b;
const allowBuckets = (_b = req.buckets) !== null && _b !== void 0 ? _b : [];
const rootFolder = this._rootFolder;
const buckets = (this._buckets == null
? fs_1.default.readdirSync(path_1.default.join(path_1.default.resolve(), rootFolder)).filter((name) => {
return fs_1.default.statSync(path_1.default.join(rootFolder, name)).isDirectory();
})
: yield this._buckets()).filter((bucket) => allowBuckets.includes(bucket) || allowBuckets[0] === '*');
const lists = [];
for (const bucket of buckets) {
const targetDir = `${rootFolder}/${bucket}`;
const structures = this._fileStructure(targetDir);
lists.push({
[bucket]: structures
});
}
const storageSync = (dirPath, buckets) => {
let totalSize = 0;
const readDirSync = (dir) => {
const fullDirectory = path_1.default.join(path_1.default.resolve(), dir);
const files = fs_1.default.readdirSync(fullDirectory, { withFileTypes: true });
for (const file of files) {
const fullPath = path_1.default.join(path_1.default.resolve(), dir, file.name);
const filePath = path_1.default.join(dir, file.name);
const stats = fs_1.default.statSync(fullPath);
if (stats.isDirectory()) {
if (!buckets.includes(filePath.replace(/\\/g, '/').split('/')[1]))
continue;
readDirSync(filePath);
continue;
}
totalSize += stats.size;
}
};
readDirSync(dirPath);
return totalSize;
};
const bytes = storageSync(this._rootFolder, buckets);
return res.ok({
buckets: buckets.length,
storage: {
bytes,
kb: Number((bytes / 1024).toFixed(2)),
mb: Number((bytes / (1024 * 1024)).toFixed(2)),
gb: Number((bytes / (1024 * 1024 * 1024)).toFixed(2))
}
});
});
this._studioPreview = ({ req, res, params }) => __awaiter(this, void 0, void 0, function* () {
var _c, _d;
const path = String(params['*']).replace(/^\/+/, '').replace(/\.{2}(?!\.)/g, "");
const [bucket, ...rest] = String(path).split('/');
const allowBuckets = req.buckets || [];
if (!(allowBuckets.includes(bucket) || allowBuckets[0] === '*')) {
return res.forbidden();
}
let filePath = rest.join('/');
if (filePath != null) {
filePath = this._normalizeFolder(String(filePath));
}
const directory = this._normalizeDirectory({ bucket, folder: null });
if (!fs_1.default.existsSync(path_1.default.join(path_1.default.resolve(), directory, filePath))) {
return res.notFound(`The directory '${path}' does not exist`);
}
const extension = path_1.default.extname(filePath).replace(/\./g, '');
const textFileExtensions = [
'txt', 'md', 'csv', 'json', 'xml', 'html', 'css', 'js', 'ts',
'java', 'go', 'rs', 'py', 'log', 'yaml', 'ini', 'bat', 'sh', 'sql',
'conf', 'rtf', 'tex', 'srt', 'plist', 'env', 'yml', 'yaml', 'key'
];
if (textFileExtensions.includes(extension)) {
const html = fs_1.default.readFileSync(path_1.default.join(__dirname, 'studio', 'vs-code.html'), 'utf8');
const minifiedHtml = yield (0, html_minifier_terser_1.minify)(html, {
collapseWhitespace: true,
removeComments: true,
minifyCSS: true,
minifyJS: true
});
const language = (_c = {
'ts': 'typescript',
'js': 'javascript'
}[extension]) !== null && _c !== void 0 ? _c : extension;
return res.html(minifiedHtml.replace('{{language}}', language));
}
const { stream, set } = yield this._makeStream({
bucket: bucket,
filePath: String(filePath),
range: (_d = req.headers) === null || _d === void 0 ? void 0 : _d.range,
download: true
});
set(res);
return stream.pipe(res);
});
this._stduioPreviewText = ({ req, res, params }) => __awaiter(this, void 0, void 0, function* () {
const path = String(params['*']).replace(/^\/+/, '').replace(/\.{2}(?!\.)/g, "");
const [bucket, ...rest] = String(path).split('/');
const allowBuckets = req.buckets || [];
if (!(allowBuckets.includes(bucket) || allowBuckets[0] === '*')) {
return res.forbidden();
}
let filePath = rest.join('/');
if (filePath != null) {
filePath = this._normalizeFolder(String(filePath));
}
const directory = this._normalizeDirectory({ bucket, folder: null });
if (!fs_1.default.existsSync(path_1.default.join(path_1.default.resolve(), directory, filePath))) {
return res.notFound(`The directory '${path}' does not exist`);
}
const text = fs_1.default.readFileSync(path_1.default.join(path_1.default.resolve(), this._rootFolder, bucket, filePath), 'utf8');
return res.send(text);
});
this._stduioPreviewTextEdit = ({ req, res, params, body }) => __awaiter(this, void 0, void 0, function* () {
if (body.content == null) {
return res.badRequest('Please enter a content for rewrite file');
}
const path = String(params['*']).replace(/^\/+/, '').replace(/\.{2}(?!\.)/g, "");
const [bucket, ...rest] = String(path).split('/');
const allowBuckets = req.buckets || [];
if (!(allowBuckets.includes(bucket) || allowBuckets[0] === '*')) {
return res.forbidden();
}
let filePath = rest.join('/');
if (filePath != null) {
filePath = this._normalizeFolder(String(filePath));
}
const directory = this._normalizeDirectory({ bucket, folder: null });
const fullPath = path_1.default.join(path_1.default.resolve(), directory, filePath);
if (!fs_1.default.existsSync(fullPath)) {
return res.notFound(`The directory '${path}' does not exist`);
}
fs_1.default.writeFileSync(fullPath, String(body.content), 'utf-8');
return res.ok();
});
this._studioLogin = ({ res, body }) => __awaiter(this, void 0, void 0, function* () {
var _e;
if (this._onStudioCredentials == null) {
return res.badRequest('Please enable the studio');
}
const { username, password } = body;
if (!username) {
return res.badRequest('Please enter an username');
}
const check = yield this._onStudioCredentials({ username: String(username), password: String(password) });
if (!(check === null || check === void 0 ? void 0 : check.logged))
return res.unauthorized('Please check your username and password');
const EXPIRED = 43200;
const session = jsonwebtoken_1.default.sign({
data: {
issuer: 'nfs-studio',
sub: {
buckets: (_e = check === null || check === void 0 ? void 0 : check.buckets) !== null && _e !== void 0 ? _e : [],
permissions: ['*'],
token: Buffer.from(`${+new Date()}`).toString('base64')
}
}
}, this._jwtSecret, {
expiresIn: EXPIRED,
algorithm: 'HS256'
});
res.setHeader('Set-Cookie', `auth.session=${session}; HttpOnly; Max-Age=${EXPIRED}; Path=/studio`);
return res.ok();
});
this._studioLogout = ({ res }) => __awaiter(this, void 0, void 0, function* () {
res.setHeader('Set-Cookie', `auth.session=; HttpOnly; Max-Age=0; Path=/studio`);
return res.ok();
});
this._studioUpload = ({ req, res, files, body }) => __awaiter(this, void 0, void 0, function* () {
try {
if (!Array.isArray(files === null || files === void 0 ? void 0 : files.file) || (files === null || files === void 0 ? void 0 : files.file[0]) == null) {
return res.badRequest('The file is required.');
}
if (body.path == null || body.path === '') {
return res.badRequest('The path is required.');
}
const file = files === null || files === void 0 ? void 0 : files.file[0];
const [bucket, ...rest] = String(body.path).split('/');
const allowBuckets = req.buckets || [];
if (!(allowBuckets.includes(bucket) || allowBuckets[0] === '*')) {
return res.forbidden();
}
if (this._buckets != null && !(yield this._buckets()).includes(bucket)) {
return res.forbidden();
}
let folder = rest.join('/');
if (folder != null) {
folder = this._normalizeFolder(String(folder));
}
const directory = this._normalizeDirectory({ bucket, folder });
if (!(yield this._fileExists(directory))) {
if (this._debug) {
console.log({ directory, bucket, folder });
}
fs_1.default.mkdirSync(directory, {
recursive: true
});
}
const writeFile = (file, to) => {
return new Promise((resolve, reject) => {
fs_1.default.createReadStream(file)
.pipe(fs_1.default.createWriteStream(to))
.on('finish', () => {
// remove temporary from server
this._remove(file, { delayMs: 0 });
return resolve(null);
})
.on('error', (err) => reject(err));
return;
});
};
const name = `${file.name}`;
yield writeFile(file.tempFilePath, this._normalizePath({ directory, path: name, full: true }));
return res.ok({
path: this._normalizePath({ directory: folder, path: name }),
name: name,
size: file.size
});
}
catch (err) {
if (this._debug) {
console.log(err);
}
throw err;
}
});
this._studioBucket = ({ req, res }) => __awaiter(this, void 0, void 0, function* () {
var _f;
const allowBuckets = (_f = req.buckets) !== null && _f !== void 0 ? _f : [];
const rootFolder = this._rootFolder;
const buckets = (this._buckets == null
? fs_1.default.readdirSync(path_1.default.join(path_1.default.resolve(), rootFolder)).filter((name) => {
return fs_1.default.statSync(path_1.default.join(rootFolder, name)).isDirectory();
})
: yield this._buckets()).filter((bucket) => allowBuckets.includes(bucket) || allowBuckets[0] === '*');
const lists = [];
const storageSync = (dirPath, bucket) => {
let totalSize = 0;
const readDirSync = (dir) => {
const fullDirectory = path_1.default.join(path_1.default.resolve(), dir);
const files = fs_1.default.readdirSync(fullDirectory, { withFileTypes: true });
for (const file of files) {
const fullPath = path_1.default.join(path_1.default.resolve(), dir, file.name);
const filePath = path_1.default.join(dir, file.name);
const stats = fs_1.default.statSync(fullPath);
if (stats.isDirectory()) {
readDirSync(filePath);
continue;
}
totalSize += stats.size;
}
};
readDirSync([dirPath, bucket].join('/'));
return totalSize;
};
for (const bucket of buckets) {
if (allowBuckets.includes(bucket) || allowBuckets[0] === '*') {
const fullPath = path_1.default.join(path_1.default.resolve(), this._rootFolder, bucket);
if (!(yield this._fileExists(fullPath))) {
fs_1.default.mkdirSync(fullPath, {
recursive: true
});
if (this._onStudioBucketCreated != null) {
const random = () => [1, 2, 3].map(v => Math.random().toString(36).substring(3)).join('');
yield this._onStudioBucketCreated({
bucket: String(bucket),
token: String(random()),
secret: String(random())
});
}
}
const loadCredentials = this._onLoadBucketCredentials == null ? [] : yield this._onLoadBucketCredentials();
const credentials = loadCredentials.find(v => v.bucket === bucket);
const bytes = storageSync(this._rootFolder, bucket);
lists.push({
[bucket]: {
credentials,
storage: {
bytes,
kb: Number((bytes / 1024).toFixed(2)),
mb: Number((bytes / (1024 * 1024)).toFixed(2)),
gb: Number((bytes / (1024 * 1024 * 1024)).toFixed(2))
}
}
});
}
}
return res.ok({
buckets: lists
});
});
this._studioBucketCreate = ({ req, res, body }) => __awaiter(this, void 0, void 0, function* () {
const { bucket, token, secret } = body;
if ([bucket, token, secret].some(v => v == null || v === '')) {
return res.badRequest('Please enter a bucket token and secret');
}
const directory = this._normalizeDirectory({ bucket: String(bucket), folder: null });
if (!(yield this._fileExists(directory))) {
fs_1.default.mkdirSync(directory, {
recursive: true
});
}
if (this._onStudioBucketCreated != null) {
yield this._onStudioBucketCreated({
bucket: String(bucket),
token: String(token),
secret: String(secret)
});
}
return res.ok();
});
this._studioFiles = ({ req, res, params }) => __awaiter(this, void 0, void 0, function* () {
const rootFolder = this._rootFolder;
const path = String(params['*']).replace(/^\/+/, '').replace(/\.{2}(?!\.)/g, "");
const [bucket] = String(path).split('/');
const allowBuckets = req.buckets || [];
if (!(allowBuckets.includes(bucket) || allowBuckets[0] === '*')) {
return res.forbidden();
}
const targetDir = `${rootFolder}/${path}`;
if (!fs_1.default.existsSync(path_1.default.join(path_1.default.resolve(), targetDir))) {
return res.notFound(`The directory '${path}' does not exist`);
}
const files = this._fileStructure(targetDir, { includeFiles: true });
return res.ok({
files: files.sort((a, b) => {
if (a.isFolder !== b.isFolder) {
return b.isFolder - a.isFolder;
}
return +new Date(a.lastModified) - +new Date(b.lastModified);
})
});
});
this._studioEdit = ({ req, res, params, body }) => __awaiter(this, void 0, void 0, function* () {
const path = String(params['*']).replace(/^\/+/, '').replace(/\.{2}(?!\.)/g, "");
const [bucket, ...rest] = String(path).split('/');
const allowBuckets = req.buckets || [];
if (!(allowBuckets.includes(bucket) || allowBuckets[0] === '*')) {
return res.forbidden();
}
const { rename } = body;
if (rename == null || rename === '') {
return res.badRequest('Please enter the name you wish to use.');
}
let filePath = rest.join('/');
if (filePath != null) {
filePath = this._normalizeFolder(String(filePath));
}
const oldPath = this._normalizeDirectory({ bucket, folder: filePath });
if (!fs_1.default.existsSync(path_1.default.join(path_1.default.resolve(), oldPath)))
return res.notFound();
const newPath = this._normalizeDirectory({
bucket,
folder: `${path_1.default.dirname(filePath)}/${rename}${path_1.default.extname(filePath)}`
});
fs_1.default.renameSync(path_1.default.join(path_1.default.resolve(), oldPath), path_1.default.join(path_1.default.resolve(), newPath));
return res.ok({
name: rename
});
});
this._studioRemove = ({ req, res, body, params }) => __awaiter(this, void 0, void 0, function* () {
const data = String(params['*']).replace(/^\/+/, '').replace(/\.{2}(?!\.)/g, "");
const [bucket, ...rest] = String(data).split('/');
const path = rest.join('/');
const allowBuckets = req.buckets || [];
if (!(allowBuckets.includes(bucket) || allowBuckets[0] === '*')) {
return res.forbidden();
}
const filePath = this._normalizeFolder(String(path));
const fullPath = path_1.default.join(path_1.default.resolve(), this._normalizeDirectory({ bucket, folder: filePath }));
if (!fs_1.default.existsSync(fullPath))
return res.notFound();
const stats = fs_1.default.statSync(fullPath);
if (stats.isDirectory()) {
if (path.includes(this._trash)) {
fs_1.default.rmSync(fullPath, { recursive: true, force: true });
return res.ok();
}
this._queue.add(() => __awaiter(this, void 0, void 0, function* () { return yield this._trashedWithFolder({ path, bucket }); }));
return res.ok();
}
if (path.includes(this._trash)) {
this._remove(fullPath, { delayMs: 0 });
return res.ok();
}
this._queue.add(() => __awaiter(this, void 0, void 0, function* () { return yield this._trashed({ path, bucket }); }));
return res.ok();
});
this._fileStructure = (dirPath, { includeFiles = false } = {}) => {
const items = [];
const files = fs_1.default.readdirSync(dirPath);
for (const file of files) {
const path = path_1.default.join(dirPath, file);
const fullPath = path_1.default.join(path_1.default.resolve(), dirPath, file);
const stats = fs_1.default.lstatSync(fullPath);
const lastModified = stats.mtime;
if (stats.isDirectory()) {
items.push({
name: file,
path: path.replace(/\\/g, '/').replace(`${this._rootFolder}/`, ''),
isFolder: true,
lastModified,
folders: this._fileStructure(path, { includeFiles })
});
continue;
}
if (!includeFiles)
continue;
const extension = path_1.default.extname(file).replace(/\./g, '');
items.push({
name: file,
path: path.replace(/\\/g, '/').replace(this._rootFolder, ''),
isFolder: false,
lastModified,
size: stats.size,
extension
});
}
return items;
};
this._authMiddleware = ({ req, res, headers }, next) => {
const authorization = String(headers.authorization).split(' ')[1];
if (authorization == null) {
return res.status(401).json({
message: 'Please check your credentials. Are they valid ?'
});
}
const { bucket, token } = this._verify(authorization);
req.bucket = bucket;
req.token = token;
return next();
};
this._authStudioMiddleware = ({ req, res, cookies }, next) => {
var _a, _b;
const authorization = cookies['auth.session'];
if (authorization == null || authorization === '') {
if ((_a = req.url) === null || _a === void 0 ? void 0 : _a.includes('/studio/preview')) {
res.writeHead(401, { 'Content-Type': 'text/xml' });
const error = {
Error: [
{ Code: 'Unauthorized' },
{ Message: 'Please check your credentials. Are they valid ?' },
{ Resource: req.url },
{ RequestKey: "" }
]
};
return res.end((0, xml_1.default)([error], { declaration: true }));
}
return res.status(401).json({
message: 'Please check your credentials. Are they valid ?'
});
}
try {
const { buckets, token } = this._verify(authorization);
req.buckets = buckets;
req.token = token;
return next();
}
catch (e) {
if ((_b = req.url) === null || _b === void 0 ? void 0 : _b.includes('/studio/preview')) {
res.writeHead(400, { 'Content-Type': 'text/xml' });
const error = {
Error: [
{ Code: 'Bad Request' },
{ Message: e.message },
{ Resource: req.url },
{ RequestKey: "" }
]
};
return res.end((0, xml_1.default)([error], { declaration: true }));
}
return res.status(400).json({
message: e.message
});
}
};
this._removeOldDirInTrash = (bucket) => __awaiter(this, void 0, void 0, function* () {
const directory = this._normalizeDirectory({ bucket, folder: this._trash });
const files = yield fs_1.default.promises.readdir(directory);
for (const file of files) {
const dir = this._normalizePath({ directory, path: file, full: true });
const stats = yield fs_1.default.promises.stat(dir);
if (!stats.isDirectory())
continue;
const format = file.match(/^\d{4}-\d{2}-\d{2}/);
const folderDate = new tspace_utils_1.Time(format ? format[0] : 0).toTimestamp();
const ago = new tspace_utils_1.Time().minusDays(this._backup).toTimeStamp();
if (Number.isNaN(folderDate) || folderDate > ago)
continue;
yield this._removeDir(dir);
}
});
this._removeDir = (path) => __awaiter(this, void 0, void 0, function* () {
return yield fs_1.default.promises
.rm(path, { recursive: true })
.catch(_ => {
return;
});
});
}
get instance() {
return this._app;
}
/**
* The 'progress' is method used to view the progress of the file upload.
*
* @returns {this}
*/
debug() {
this._debug = true;
return this;
}
/**
* The 'progress' is method used to view the progress of the file upload.
*
* @returns {this}
*/
progress() {
this._progress = true;
return this;
}
/**
* The