diff --git a/.swcrc b/.swcrc index b59ddb2..e8ffa5b 100644 --- a/.swcrc +++ b/.swcrc @@ -10,7 +10,7 @@ "topLevel": false } }, - "target": "es2021", + "target": "esnext", "parser": { "syntax": "typescript", "tsx": false, diff --git a/src/controllers/rest/impl/FileUploadController.ts b/src/controllers/rest/impl/FileUploadController.ts index a1ea96c..f363dcd 100644 --- a/src/controllers/rest/impl/FileUploadController.ts +++ b/src/controllers/rest/impl/FileUploadController.ts @@ -5,9 +5,8 @@ import { FileUploadModelResponse } from "../../../model/rest/FileUploadModelResp import { BadRequest, Forbidden, UnsupportedMediaType } from "@tsed/exceptions"; import { MultipartFile, PathParams, type PlatformMulterFile, QueryParams, Req, Res } from "@tsed/common"; import { BodyParams } from "@tsed/platform-params"; -import { FileEngine } from "../../../engine/impl/index.js"; import { FileService } from "../../../services/FileService.js"; -import { NetworkUtils } from "../../../utils/Utils.js"; +import { FileUtils, NetworkUtils } from "../../../utils/Utils.js"; import { BaseRestController } from "../BaseRestController.js"; import { Logger } from "@tsed/logger"; @@ -17,7 +16,6 @@ import { Logger } from "@tsed/logger"; @Returns(StatusCodes.FORBIDDEN, Forbidden).Description("If your IP has been blocked") export class FileUploadController extends BaseRestController { public constructor( - @Inject() private fileEngine: FileEngine, @Inject() private fileUploadService: FileService, @Inject() private logger: Logger, ) { @@ -75,7 +73,7 @@ export class FileUploadController extends BaseRestController { ): Promise { if (file && url) { if (file) { - await this.fileEngine.deleteFile(file); + await FileUtils.deleteFile(file); } throw new BadRequest("Unable to upload both a file and a url"); } @@ -106,7 +104,7 @@ export class FileUploadController extends BaseRestController { if (file) { // this will delete files if something goes wrong, but not urls... // TODO: fix - await this.fileEngine.deleteFile(file, true); + await FileUtils.deleteFile(file, true); } throw e; } diff --git a/src/engine/impl/FileEngine.ts b/src/engine/impl/FileEngine.ts deleted file mode 100644 index 15eecde..0000000 --- a/src/engine/impl/FileEngine.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Injectable, ProviderScope } from "@tsed/di"; -import fs from "node:fs/promises"; -import type { PlatformMulterFile } from "@tsed/common"; -import { filesDir } from "../../utils/Utils.js"; -import { FileUploadModel } from "../../model/db/FileUpload.model.js"; - -@Injectable({ - scope: ProviderScope.SINGLETON, -}) -export class FileEngine { - public deleteFile(file: string | PlatformMulterFile, force = true): Promise { - const toDelete = this.getFilePath(file); - return fs.rm(toDelete, { recursive: true, force }); - } - - public async getFileSize(file: string | PlatformMulterFile): Promise { - const f = this.getFilePath(file); - const stat = await fs.stat(f); - return stat.size; - } - - public getFilePath(file: string | PlatformMulterFile | FileUploadModel): string { - if (file instanceof FileUploadModel) { - return file.fullLocationOnDisk; - } - return typeof file === "string" ? `${filesDir}/${file}` : file.path; - } - - public async fileExists(file: string): Promise { - try { - await fs.access(file, fs.constants.F_OK); - return true; - } catch { - return false; - } - } -} diff --git a/src/engine/impl/index.ts b/src/engine/impl/index.ts index fc49f93..082d735 100644 --- a/src/engine/impl/index.ts +++ b/src/engine/impl/index.ts @@ -2,7 +2,6 @@ * @file Automatically generated by barrelsby. */ -export * from "./FileEngine.js"; export * from "./av/ClamAvEngine.js"; export * from "./av/MsDefenderEngine.js"; export * from "./HttpErrorRenderers/AuthenticationErrorRenderEngine.js"; diff --git a/src/manager/AvManager.ts b/src/manager/AvManager.ts index 24e1c72..2680ef3 100644 --- a/src/manager/AvManager.ts +++ b/src/manager/AvManager.ts @@ -2,11 +2,11 @@ import { Inject, Injectable, OnInit } from "@tsed/di"; import { AvFactory } from "../factory/AvFactory.js"; import type { PlatformMulterFile } from "@tsed/common"; import { Logger } from "@tsed/logger"; -import { FileEngine } from "../engine/impl/index.js"; import { BadRequest } from "@tsed/exceptions"; import path from "node:path"; import { IAvEngine } from "../engine/IAvEngine.js"; import { AvScanResult } from "../utils/typeings.js"; +import { FileUtils } from "../utils/Utils.js"; @Injectable() export class AvManager implements OnInit { @@ -15,7 +15,6 @@ export class AvManager implements OnInit { public constructor( @Inject() private avFactory: AvFactory, @Inject() private logger: Logger, - @Inject() private fileEngine: FileEngine, ) {} public async $onInit(): Promise { @@ -42,10 +41,10 @@ export class AvManager implements OnInit { if (scanResult.passed) { continue; } - const fileExists = await this.fileEngine.fileExists(resource); + const fileExists = await FileUtils.fileExists(resource); if (fileExists) { try { - await this.fileEngine.deleteFile(file, false); + await FileUtils.deleteFile(file, false); } catch (e) { // this basically means we could not delete the virus... this.logger.error(`Unable to delete resource ${resource} after positive AV detection`); diff --git a/src/public/assets/custom/css/index.css b/src/public/assets/custom/css/index.css index 21fc884..93f9bf4 100644 --- a/src/public/assets/custom/css/index.css +++ b/src/public/assets/custom/css/index.css @@ -117,6 +117,6 @@ font-size: 2rem; } -.am5-modal-content{ +.am5-modal-content { color: #000000 !important; } diff --git a/src/public/assets/custom/js/main.js b/src/public/assets/custom/js/main.js index d09bc10..31bf5b1 100644 --- a/src/public/assets/custom/js/main.js +++ b/src/public/assets/custom/js/main.js @@ -1,4 +1,4 @@ -const Site = (function () { +const Site = (function() { let isInit = false; const loading = function loading(show) { @@ -52,14 +52,14 @@ const Site = (function () { // eslint-disable-next-line require-await anon.call(this, Site).then(async () => { function initTooltips() { - document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)); + document.querySelectorAll("[data-bs-toggle=\"tooltip\"]").forEach(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)); } function initTabs() { - const triggerTabList = document.querySelectorAll('#resultTabs button'); + const triggerTabList = document.querySelectorAll("#resultTabs button"); triggerTabList.forEach(triggerEl => { const tabTrigger = new bootstrap.Tab(triggerEl); - triggerEl.addEventListener('click', event => { + triggerEl.addEventListener("click", event => { event.preventDefault(); tabTrigger.show(); }); @@ -76,6 +76,6 @@ const Site = (function () { loading, display, showSuccess, - showError, + showError }; }()); diff --git a/src/public/assets/custom/js/spinWheel.js b/src/public/assets/custom/js/spinWheel.js deleted file mode 100644 index 4e82d1e..0000000 --- a/src/public/assets/custom/js/spinWheel.js +++ /dev/null @@ -1,141 +0,0 @@ -class SpinWheel { - - #padding; - #w; - #h; - #r; - #rotation; - #oldrotation; - #picked; - #oldpick; - #color; - #data; - #container; - #vis; - #element; - - constructor(data, element) { - this.#padding = {top: 20, right: 40, bottom: 0, left: 0}; - this.#w = 500 - this.#padding.left - this.#padding.right; - this.#h = 500 - this.#padding.top - this.#padding.bottom; - this.#r = Math.min(this.#w, this.#h) / 2; - this.#rotation = 0; - this.#oldrotation = 0; - this.#picked = 100000; - this.#oldpick = []; - this.#color = d3.scale.category20(); - this.#data = data; - this.#element = element; - } - - draw() { - this.#createSVG(); - this.#createPieChart(); - } - - #createSVG() { - const svg = d3 - .select(this.#element) - .append('svg') - .data([this.#data]) - .attr('width', this.#w + this.#padding.left + this.#padding.right) - .attr('height', this.#h + this.#padding.top + this.#padding.bottom); - - this.#container = svg - .append('g') - .attr('class', 'chartholder') - .attr( - 'transform', - `translate(${this.#w / 2 + this.#padding.left},${this.#h / 2 + this.#padding.top})` - ); - - this.#vis = this.#container.append('g'); - - svg.append("g") - .attr("transform", "translate(" + (this.#w + this.#padding.left + this.#padding.right) + "," + ((this.#h / 2) + this.#padding.top) + ")") - .append("path") - .attr("d", "M-" + (this.#r * .15) + ",0L0," + (this.#r * .05) + "L0,-" + (this.#r * .05) + "Z") - .style({"fill": "black"}); - this.#container.append("circle") - .attr("cx", 0) - .attr("cy", 0) - .attr("r", 60) - .style({"fill": "white", "cursor": "pointer"}); - this.#container.append("text") - .attr("x", 0) - .attr("y", 15) - .attr("text-anchor", "middle") - .text("SPIN") - .style({"font-weight": "bold", "font-size": "30px"}); - } - - #createPieChart() { - const pie = d3.layout.pie().sort(null).value(() => 1); - const arc = d3.svg.arc().outerRadius(this.#r); - - const arcs = this.#vis - .selectAll('g.slice') - .data(pie(this.#data)) - .enter() - .append('g') - .attr('class', 'slice'); - - arcs - .append('path') - .attr('fill', (d, i) => this.#color(i)) - .attr('d', d => arc(d)); - - arcs - .append('text') - .attr('transform', d => { - d.innerRadius = 0; - d.outerRadius = this.#r; - d.angle = (d.startAngle + d.endAngle) / 2; - return `rotate(${(d.angle * 180) / Math.PI - 90})translate(${d.outerRadius - 10})`; - }) - .attr('text-anchor', 'end') - .text((d, i) => `${this.#data[i].wadName}: ${this.#data[i].wadLevel}`); - - } - - spin() { - if (this.#oldpick.length === this.#data.length) { - console.log('done'); - this.#container.on('click', null); - return; - } - const ps = 360 / this.#data.length; - const rng = Math.floor(Math.random() * 1440) + 360; - - this.#rotation = Math.round(rng / ps) * ps; - - this.#picked = Math.round(this.#data.length - (this.#rotation % 360) / ps); - this.#picked = this.#picked >= this.#data.length ? this.#picked % this.#data.length : this.#picked; - if (this.#oldpick.indexOf(this.#picked) !== -1) { - d3.select(this).call(() => this.spin()); - return; - } else { - this.#oldpick.push(this.#picked); - } - this.#rotation += 90 - Math.round(ps / 2); - return new Promise((resolve) => { - this.#vis - .transition() - .duration(3000) - .attrTween('transform', () => this.#rotTween()) - .each('end', () => { - d3.select(".slice:nth-child(" + (this.#picked + 1) + ") path").attr("fill", "#111"); - this.#oldrotation = this.#rotation; - this.#container.on("click", () => this.spin()); - resolve(this.#data[this.#picked]); - }); - }); - } - - #rotTween() { - const i = d3.interpolate(this.#oldrotation % 360, this.#rotation); - return function (t) { - return `rotate(${i(t)})`; - }; - } -} diff --git a/src/public/fileLogin.ejs b/src/public/fileLogin.ejs index 01c4c9c..a45a9d8 100644 --- a/src/public/fileLogin.ejs +++ b/src/public/fileLogin.ejs @@ -52,8 +52,8 @@ submitPassword(); }); - function progress({loaded, total}) { - const downloadDone = Math.round(loaded / total * 100) + '%'; + function progress({ loaded, total }) { + const downloadDone = Math.round(loaded / total * 100) + "%"; progressbar.innerText = downloadDone; progressbar.style.width = downloadDone; progressWrapper.setAttribute("aria-valuenow", Math.round(loaded / total * 100).toString()); @@ -78,35 +78,35 @@ const response = await fetch(url, { headers: { "x-password": password - }, + } }); if (response.status === 403) { alert.classList.remove("hidden"); return; } - const contentLength = response.headers.get('content-length'); + const contentLength = response.headers.get("content-length"); const total = Number.parseInt(contentLength); let loaded = 0; const res = new Response( new ReadableStream({ - async start(controller) { - const reader = response.body.getReader(); - for (; ;) { - try { - const {done, value} = await reader.read(); - if (done) { - break; - } - loaded += value.byteLength; - progress({loaded, total}); - controller.enqueue(value); - } catch (e) { - controller.error(e); + async start(controller) { + const reader = response.body.getReader(); + for (; ;) { + try { + const { done, value } = await reader.read(); + if (done) { + break; + } + loaded += value.byteLength; + progress({ loaded, total }); + controller.enqueue(value); + } catch (e) { + controller.error(e); return; } + } + controller.close(); } - controller.close(); - }, } ), { headers: response.headers, diff --git a/src/public/index.ejs b/src/public/index.ejs index 273ab58..2f0cb8f 100644 --- a/src/public/index.ejs +++ b/src/public/index.ejs @@ -12,7 +12,7 @@ WaifuVault - Source + Source
@@ -22,7 +22,7 @@ https://0x0.st, WaifuVault is a temporary file hosting service that allows for file uploads that are hosted for a set amount of time.

Api Documentation - logo + logo
diff --git a/src/public/login.ejs b/src/public/login.ejs index b838b9e..d902b75 100644 --- a/src/public/login.ejs +++ b/src/public/login.ejs @@ -62,12 +62,12 @@ <% if(typeof internalError !== "undefined") { %> - @@ -76,10 +76,10 @@ <%- include('snippets/scripts.ejs'); %> diff --git a/src/public/secure/files.ejs b/src/public/secure/files.ejs index 1832c63..8fc59ea 100644 --- a/src/public/secure/files.ejs +++ b/src/public/secure/files.ejs @@ -4,7 +4,6 @@ <%- include('../snippets/head.ejs'); %> - @@ -67,14 +67,14 @@ function summarizeProperty(entries, property, isDate = false, includeNull = false) { entries = includeNull ? entries : entries.filter(entry => entry[property]); const counts = entries.reduce((acc, entry) => { - const key = isDate ? new Date(entry[property].split('T')[0]).getTime() : entry[property]; + const key = isDate ? new Date(entry[property].split("T")[0]).getTime() : entry[property]; acc[key] = (acc[key] || 0) + 1; return acc; }, {}); - if(isDate) { + if (isDate) { return Object.entries(counts).map(([date, value]) => ({ - date:Number(date), value + date: Number(date), value })); } @@ -95,7 +95,7 @@ let placed = false; for (let i = 0; i < bands.length; i++) { - if (Math.floor(value/(1024*1024)) <= bands[i]) { + if (Math.floor(value / (1024 * 1024)) <= bands[i]) { bandCounts[i].count += 1; placed = true; break; @@ -111,7 +111,7 @@ return bandCounts; } - function showEmptyModal(ev,seriesobj,modal,ticks) { + function showEmptyModal(ev, seriesobj, modal, ticks) { const series = ev.target; if (ev.target.data.length < 1) { // Generate placeholder data @@ -122,7 +122,7 @@ let item = {}; item[categoryField] = ""; item[valueField] = 1; - placeholder.push(item) + placeholder.push(item); } seriesobj.data.setAll(placeholder); @@ -134,8 +134,7 @@ // Show modal modal.open(); - } - else { + } else { // Re-enable ticks/labels if (ticks) { seriesobj.labels.template.set("forceHidden", false); @@ -149,26 +148,26 @@ async function getStats() { try { - const resp = await fetch('/rest/admin/statsData'); + const resp = await fetch("/rest/admin/statsData"); if (!resp.ok) { throw new Error(`Network response wrong - ${resp.statusText}`); } - return await resp.json() + return await resp.json(); } catch (err) { console.error(`Fetch failed: ${err}`); return null; } } - am5.ready(function () { + am5.ready(function() { getStats().then(stats => { - if(stats.totalFileCount === stats.realFileCount) { - document.getElementById('totalFiles').innerText = stats.totalFileCount; + if (stats.totalFileCount === stats.realFileCount) { + document.getElementById("totalFiles").innerText = stats.totalFileCount; } else { - document.getElementById('totalFiles').innerText = `${stats.totalFileCount} (${stats.realFileCount} on filesystem)`; - document.getElementById('totalFiles').className = "text-danger"; + document.getElementById("totalFiles").innerText = `${stats.totalFileCount} (${stats.realFileCount} on filesystem)`; + document.getElementById("totalFiles").className = "text-danger"; } - document.getElementById('totalSize').innerText = sizeReadable(stats.totalFileSize); + document.getElementById("totalSize").innerText = sizeReadable(stats.totalFileSize); //File Protection const rootProt = am5.Root.new("chartProtectionDistribution"); @@ -176,7 +175,7 @@ rootProt.interfaceColors.set("text", am5.color(0xffffff)); const chartProt = rootProt.container.children.push(am5percent.PieChart.new(rootProt, { layout: rootProt.verticalHorizontal, - radius:am5.percent(70) + radius: am5.percent(70) })); const modalProt = am5.Modal.new(rootProt, { content: "The chart has no data" @@ -188,18 +187,18 @@ alignLabels: false })); seriesProt.events.on("datavalidated", function(ev) { - showEmptyModal(ev,seriesProt,modalProt,true); + showEmptyModal(ev, seriesProt, modalProt, true); }); - seriesProt.data.setAll(summarizeProperty(stats.entries, 'fileProtectionLevel')); + seriesProt.data.setAll(summarizeProperty(stats.entries, "fileProtectionLevel")); //File Size - const sizeBands = [1,10,100,250,450]; + const sizeBands = [1, 10, 100, 250, 450]; const rootSize = am5.Root.new("chartSizeDistribution"); rootSize.interfaceColors.set("grid", am5.color(0xffffff)); rootSize.interfaceColors.set("text", am5.color(0xffffff)); const chartSize = rootSize.container.children.push(am5percent.PieChart.new(rootSize, { layout: rootSize.verticalHorizontal, - radius:am5.percent(70) + radius: am5.percent(70) })); const modalSize = am5.Modal.new(rootSize, { content: "The chart has no data" @@ -211,17 +210,17 @@ alignLabels: false })); seriesSize.events.on("datavalidated", function(ev) { - showEmptyModal(ev,seriesSize,modalSize,true); + showEmptyModal(ev, seriesSize, modalSize, true); }); - seriesSize.data.setAll(summarizeInBands(stats.entries,'fileSize',sizeBands).filter(i => i.count !== 0)); + seriesSize.data.setAll(summarizeInBands(stats.entries, "fileSize", sizeBands).filter(i => i.count !== 0)); //Media Type const rootMedia = am5.Root.new("chartMimeDistribution"); rootMedia.interfaceColors.set("grid", am5.color(0xffffff)); rootMedia.interfaceColors.set("text", am5.color(0xffffff)); const chartMedia = rootMedia.container.children.push(am5percent.PieChart.new(rootMedia, { - layout:rootMedia.horizontalLayout, - radius:am5.percent(70) + layout: rootMedia.horizontalLayout, + radius: am5.percent(70) })); const legendMedia = chartMedia.children.push(am5.Legend.new(rootMedia, {})); legendMedia.data.setAll(chartMedia.series.values); @@ -235,18 +234,18 @@ alignLabels: false })); seriesMedia.events.on("datavalidated", function(ev) { - showEmptyModal(ev,seriesMedia,modalMedia,true); + showEmptyModal(ev, seriesMedia, modalMedia, true); }); - const mediaItems = summarizeProperty(stats.entries, 'mediaType', false, false); - mediaItems.sort((a,b) => b.value - a.value); - seriesMedia.data.setAll(mediaItems.slice(0,10)); + const mediaItems = summarizeProperty(stats.entries, "mediaType", false, false); + mediaItems.sort((a, b) => b.value - a.value); + seriesMedia.data.setAll(mediaItems.slice(0, 10)); //Upload velocity const rootUpload = am5.Root.new("chartUploadVelocity"); rootUpload.interfaceColors.set("grid", am5.color(0xffffff)); rootUpload.interfaceColors.set("text", am5.color(0xffffff)); const uploadTheme = am5.Theme.new(rootUpload); - uploadTheme.rule("AxisLabel",["minor"]).setAll({dy:1}); + uploadTheme.rule("AxisLabel", ["minor"]).setAll({ dy: 1 }); rootUpload.setThemes([ am5themes_Animated.new(rootUpload), uploadTheme, @@ -267,14 +266,14 @@ count: 1 }, renderer: am5xy.AxisRendererX.new(rootUpload, { - minorGridEnabled:true, - minorLabelsEnabled:true + minorGridEnabled: true, + minorLabelsEnabled: true }), tooltip: am5.Tooltip.new(rootUpload, {}) })); xAxis.set("minorDateFormats", { - "day":"dd", - "month":"MM" + "day": "dd", + "month": "MM" }); const yAxis = chartUpload.yAxes.push(am5xy.ValueAxis.new(rootUpload, { renderer: am5xy.AxisRendererY.new(rootUpload, {}) @@ -293,10 +292,10 @@ }) })); seriesUpload.events.on("datavalidated", function(ev) { - showEmptyModal(ev,seriesUpload,modalUpload,false); + showEmptyModal(ev, seriesUpload, modalUpload, false); }); - seriesUpload.columns.template.setAll({ strokeOpacity: 0 }) - seriesUpload.data.setAll(summarizeProperty(stats.entries, 'createdAt', true)); + seriesUpload.columns.template.setAll({ strokeOpacity: 0 }); + seriesUpload.data.setAll(summarizeProperty(stats.entries, "createdAt", true)); seriesUpload.appear(1000); chartUpload.appear(1000, 100); }); diff --git a/src/public/secure/user.ejs b/src/public/secure/user.ejs index b34dd20..89cc66a 100644 --- a/src/public/secure/user.ejs +++ b/src/public/secure/user.ejs @@ -12,7 +12,7 @@
-<%- include('../snippets/navbar.ejs'); %> + <%- include('../snippets/navbar.ejs'); %>
@@ -63,7 +63,7 @@ Site.loading(true); try { await fetch(`${baseUrl}/auth/changeDetails`, { - method: 'POST', + method: "POST", body: formData }); } catch (e) { @@ -81,4 +81,4 @@ }); - \ No newline at end of file + diff --git a/src/services/EncryptionService.ts b/src/services/EncryptionService.ts index efe5059..41e226e 100644 --- a/src/services/EncryptionService.ts +++ b/src/services/EncryptionService.ts @@ -1,13 +1,13 @@ -import { Constant, Inject, OnInit, Service } from "@tsed/di"; +import { Constant, OnInit, Service } from "@tsed/di"; import { FileUploadModel } from "../model/db/FileUpload.model.js"; import fs from "node:fs/promises"; -import { FileEngine } from "../engine/impl/index.js"; import crypto from "node:crypto"; import argon2 from "argon2"; import { Forbidden } from "@tsed/exceptions"; import Path from "node:path"; import GlobalEnv from "../model/constants/GlobalEnv.js"; import { promisify } from "node:util"; +import { FileUtils } from "../utils/Utils.js"; @Service() export class EncryptionService implements OnInit { @@ -18,8 +18,6 @@ export class EncryptionService implements OnInit { @Constant(GlobalEnv.SALT) private readonly salt: string | undefined; - public constructor(@Inject() private fileEngine: FileEngine) {} - private getKey(password: string): Promise { return argon2.hash(password, { hashLength: 32, @@ -32,7 +30,7 @@ export class EncryptionService implements OnInit { if (!this.salt) { return false; } - const fileSource = this.fileEngine.getFilePath(Path.basename(filePath)); + const fileSource = FileUtils.getFilePath(Path.basename(filePath)); const buffer = await fs.readFile(fileSource); const iv = await this.randomBytes(16); const key = await this.getKey(password); @@ -43,7 +41,7 @@ export class EncryptionService implements OnInit { } public async decrypt(source: FileUploadModel, password?: string): Promise { - const fileSource = this.fileEngine.getFilePath(source); + const fileSource = FileUtils.getFilePath(source); const fileBuffer = await fs.readFile(fileSource); const isEncrypted = source.encrypted; if (!source.settings?.password) { diff --git a/src/services/FileCleaner.ts b/src/services/FileCleaner.ts index 61fca01..ba00959 100644 --- a/src/services/FileCleaner.ts +++ b/src/services/FileCleaner.ts @@ -5,7 +5,6 @@ import { FileService } from "./FileService.js"; import { filesDir, FileUtils } from "../utils/Utils.js"; import GlobalEnv from "../model/constants/GlobalEnv.js"; import fs from "node:fs/promises"; -import { FileEngine } from "../engine/impl/index.js"; import { FileUploadModel } from "../model/db/FileUpload.model.js"; @Service() @@ -14,7 +13,6 @@ export class FileCleaner implements OnInit { @Inject() private repo: FileRepo, @Inject() private scheduleService: ScheduleService, @Inject() private fileUploadService: FileService, - @Inject() private fileEngine: FileEngine, ) {} // default to every hour at :00 @@ -51,7 +49,7 @@ export class FileCleaner implements OnInit { const allFilesFromSystem = await fs.readdir(filesDir); const deleteFilesPromises = allFilesFromSystem .filter(fileOnSystem => !this.isFileInDb(allFilesFromDb, fileOnSystem)) - .map(fileToDelete => this.fileEngine.deleteFile(fileToDelete)); + .map(fileToDelete => FileUtils.deleteFile(fileToDelete)); await Promise.all(deleteFilesPromises); } diff --git a/src/services/FileService.ts b/src/services/FileService.ts index 0708c9c..c7265eb 100644 --- a/src/services/FileService.ts +++ b/src/services/FileService.ts @@ -2,7 +2,6 @@ import { Constant, Inject, Service } from "@tsed/di"; import { FileRepo } from "../db/repo/FileRepo.js"; import type { PlatformMulterFile } from "@tsed/common"; import { FileUploadModel } from "../model/db/FileUpload.model.js"; -import { FileEngine } from "../engine/impl/index.js"; import { FileUrlService } from "./FileUrlService.js"; import { MimeService } from "./MimeService.js"; import { Builder, type IBuilder } from "builder-pattern"; @@ -30,7 +29,6 @@ export class FileService { public constructor( @Inject() private repo: FileRepo, - @Inject() private fileEngine: FileEngine, @Inject() private fileUrlService: FileUrlService, @Inject() private mimeService: MimeService, @Inject() private logger: Logger, @@ -54,7 +52,7 @@ export class FileService { await this.checkMime(resourcePath); const mediaType = await this.mimeService.findMimeType(resourcePath); uploadEntry.mediaType(mediaType); - const fileSize = await this.fileEngine.getFileSize(path.basename(resourcePath)); + const fileSize = await FileUtils.getFileSize(path.basename(resourcePath)); uploadEntry.fileSize(fileSize); const checksum = await this.getFileHash(resourcePath); @@ -215,10 +213,10 @@ export class FileService { const fileDeletePArr = entries.map(entry => { if (entry.hasExpired && softDelete) { - this.fileEngine.deleteFile(entry.fullFileNameOnSystem, true); + FileUtils.deleteFile(entry.fullFileNameOnSystem, true); return Promise.reject("Entry does not exist"); } - return this.fileEngine.deleteFile(entry.fullFileNameOnSystem, true); + return FileUtils.deleteFile(entry.fullFileNameOnSystem, true); }); try { await Promise.all(fileDeletePArr); @@ -257,6 +255,6 @@ export class FileService { } private deleteUploadedFile(resource: string): Promise { - return this.fileEngine.deleteFile(path.basename(resource)); + return FileUtils.deleteFile(path.basename(resource)); } } diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 9a99ecc..d027577 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -1,10 +1,11 @@ import { fileURLToPath } from "node:url"; import path from "node:path"; -import type { FileUploadModel } from "../model/db/FileUpload.model.js"; import TIME_UNIT from "../model/constants/TIME_UNIT.js"; import process from "node:process"; import type { Request } from "express"; import fs from "node:fs/promises"; +import type { PlatformMulterFile } from "@tsed/common"; +import { FileUploadModel } from "../model/db/FileUpload.model.js"; export class ObjectUtils { public static getNumber(source: string): number { @@ -113,6 +114,33 @@ export class FileUtils { return 0; } } + + public static deleteFile(file: string | PlatformMulterFile, force = true): Promise { + const toDelete = this.getFilePath(file); + return fs.rm(toDelete, { recursive: true, force }); + } + + public static async getFileSize(file: string | PlatformMulterFile): Promise { + const f = this.getFilePath(file); + const stat = await fs.stat(f); + return stat.size; + } + + public static getFilePath(file: string | PlatformMulterFile | FileUploadModel): string { + if (file instanceof FileUploadModel) { + return file.fullLocationOnDisk; + } + return typeof file === "string" ? `${filesDir}/${file}` : file.path; + } + + public static async fileExists(file: string): Promise { + try { + await fs.access(file, fs.constants.F_OK); + return true; + } catch { + return false; + } + } } export class NetworkUtils { diff --git a/tsconfig.json b/tsconfig.json index d34142f..818d41a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ "outDir": "dist", "module": "ESNext", "forceConsistentCasingInFileNames": true, - "target": "ES2021", + "target": "ESNext", "sourceMap": true, "declaration": false, "experimentalDecorators": true,