diff --git a/app/lib/db.ts b/app/lib/db.ts index adc4ff9..42da724 100644 --- a/app/lib/db.ts +++ b/app/lib/db.ts @@ -8,62 +8,74 @@ mkdirp.sync("./var/db"); export const db = new sqlite3.Database("./var/db/media.db"); /**Create the database schema for the embedders app*/ -export function createDatabase(version: number){ +export function createDatabase(version: number) { console.log("Creating database"); - db.run("CREATE TABLE IF NOT EXISTS users ( \ + db.run( + "CREATE TABLE IF NOT EXISTS users ( \ id INTEGER PRIMARY KEY, \ username TEXT UNIQUE, \ hashed_password BLOB, \ expire INTEGER, \ salt BLOB \ - )", () => createUser("admin", process.env.EBPASS || "changeme")); + )", + () => createUser("admin", process.env.EBPASS || "changeme") + ); - db.run("CREATE TABLE IF NOT EXISTS media ( \ + db.run( + "CREATE TABLE IF NOT EXISTS media ( \ id INTEGER PRIMARY KEY, \ path TEXT NOT NULL, \ expire INTEGER, \ username TEXT \ - )"); + )" + ); db.run(`PRAGMA user_version = ${version}`); } /**Updates old Database schema to new */ -export function updateDatabase(oldVersion: number, newVersion: number){ +export function updateDatabase(oldVersion: number, newVersion: number) { if (oldVersion == 1) { console.log(`Updating database from ${oldVersion} to ${newVersion}`); db.run("PRAGMA user_version = 2", (err) => { - if(err) return; + if (err) return; }); db.run("ALTER TABLE media ADD COLUMN username TEXT", (err) => { - if(err) return; + if (err) return; }); - + db.run("ALTER TABLE users ADD COLUMN expire TEXT", (err) => { - if(err) return; + if (err) return; }); } } /**Inserts into the media table */ -export function insertToDB (filename: string, expireDate: Date, username: string) { - const params: MediaParams = [ - filename, - expireDate, - username - ]; - - db.run("INSERT INTO media (path, expire, username) VALUES (?, ?, ?)", params, function (err) { - if (err) { - console.log(err); - return err; - } - console.log(`Uploaded ${filename} to database`); - if (expireDate == null) - console.log("It will not expire"); - else if (expireDate != null || expireDate != undefined) - console.log(`It will expire on ${expireDate}`); +export function insertToDB( + filename: string, + expireDate: Date, + username: string +): Promise { + return new Promise((resolve, reject) => { + const params: MediaParams = [filename, expireDate, username]; + + db.run( + "INSERT INTO media (path, expire, username) VALUES (?, ?, ?)", + params, + function (err) { + if (err) { + console.log(err); + reject(err); + } else { + console.log(`Uploaded ${filename} to database`); + if (expireDate == null) console.log("It will not expire"); + else if (expireDate != null || expireDate != undefined) + console.log(`It will expire on ${expireDate}`); + resolve(); + } + } + ); }); } @@ -74,7 +86,7 @@ export function searchImages(imagename: string, partial: boolean) { }); } -export function updateImageName(oldimagename: string, newname:string) { +export function updateImageName(oldimagename: string, newname: string) { return new Promise((resolve, reject) => { console.log(`updating ${oldimagename} to ${newname}`); }); @@ -85,12 +97,11 @@ export function createUser(username: string, password: string) { return new Promise((resolve, reject) => { console.log(`Creating user ${username}`); const salt = crypto.randomBytes(16); - - db.run("INSERT OR IGNORE INTO users (username, hashed_password, salt) VALUES (?, ?, ?)", [ - username, - crypto.pbkdf2Sync(password, salt, 310000, 32, "sha256"), - salt - ]); + + db.run( + "INSERT OR IGNORE INTO users (username, hashed_password, salt) VALUES (?, ?, ?)", + [username, crypto.pbkdf2Sync(password, salt, 310000, 32, "sha256"), salt] + ); resolve(null); }); @@ -102,7 +113,9 @@ export function getPath(id: number | string) { const query = "SELECT path FROM media WHERE id = ?"; db.get(query, [id], (err: Error, path: object) => { - if (err) {reject(err);} + if (err) { + reject(err); + } resolve(path); }); }); @@ -114,14 +127,17 @@ export function deleteId(database: string, id: number | string) { const query = `DELETE FROM ${database} WHERE id = ?`; db.run(query, [id], (err: Error) => { - if (err) {reject(err); return;} + if (err) { + reject(err); + return; + } resolve(null); }); }); } /**Expires a database row given a Date in unix time */ -export function expire(database: string, column: string, expiration:number) { +export function expire(database: string, column: string, expiration: number) { return new Promise((resolve, reject) => { const query = `SELECT * FROM ${database} WHERE ${column} < ?`; @@ -136,30 +152,26 @@ export function expire(database: string, column: string, expiration:number) { /**A generic database row */ export interface GenericRow { - id? : number | string, - username?: string - expire? :Date + id?: number | string; + username?: string; + expire?: Date; } /**A row for the media database */ export interface MediaRow { - id? : number | string, - path: string, - expire: Date, - username?: string + id?: number | string; + path: string; + expire: Date; + username?: string; } /**Params type for doing work with media database */ -export type MediaParams = [ - path: string, - expire: Date, - username?: string -] +export type MediaParams = [path: string, expire: Date, username?: string]; /**A row for the user database */ export interface UserRow { - id? : number | string, - username: string, - hashed_password: any, - salt: any -} \ No newline at end of file + id?: number | string; + username: string; + hashed_password: any; + salt: any; +} diff --git a/app/lib/ffmpeg.ts b/app/lib/ffmpeg.ts index 45d70ae..0f3a938 100644 --- a/app/lib/ffmpeg.ts +++ b/app/lib/ffmpeg.ts @@ -54,8 +54,8 @@ export const setEncodingType = (type: EncodingType) => { const getExecutablePath = ( envVar: string, executable: string, - installer: { path: string } -) => { + installer: { path: string }, +): string => { if (process.env[envVar]) { return process.env[envVar]; } @@ -70,12 +70,13 @@ const getExecutablePath = ( const ffmpegPath = getExecutablePath( "EB_FFMPEG_PATH", "ffmpeg", - ffmpegInstaller + ffmpegInstaller, ); + const ffprobePath = getExecutablePath( "EB_FFPROBE_PATH", "ffprobe", - ffprobeInstaller + ffprobeInstaller, ); console.log(`Using ffmpeg from path: ${ffmpegPath}`); @@ -89,14 +90,14 @@ const checkEnvForEncoder = () => { if (envEncoder && Object.keys(EncodingType).includes(envEncoder)) { setEncodingType( - EncodingType[envEncoder as keyof typeof EncodingType] as EncodingType + EncodingType[envEncoder as keyof typeof EncodingType] as EncodingType, ); console.log( - `Setting encoding type to ${envEncoder} based on environment variable.` + `Setting encoding type to ${envEncoder} based on environment variable.`, ); } else if (envEncoder) { console.warn( - `Invalid encoder value "${envEncoder}" in environment variable, defaulting to CPU.` + `Invalid encoder value "${envEncoder}" in environment variable, defaulting to CPU.`, ); } }; @@ -121,7 +122,7 @@ checkEnvForEncoder(); export const ffmpegDownscale = ( path: string, filename: string, - extension: string + extension: string, ) => { const startTime = Date.now(); const outputOptions = [ @@ -136,32 +137,31 @@ export const ffmpegDownscale = ( ]; return new Promise((resolve, reject) => { + const progressFile = `uploads/${filename}${extension}-progress.json`; + ffmpeg() .input(path) .outputOptions(outputOptions) .output(`uploads/720p-${filename}${extension}`) - .on("start", () => { - // Create the .processing file - fs.closeSync( - fs.openSync(`uploads/720p-${filename}${extension}.processing`, "w") - ); + .on("progress", function(progress) { + fs.writeFileSync(progressFile, JSON.stringify({ progress: progress.percent / 100 })); }) .on("end", () => { console.log( `720p copy complete using ${currentEncoding}, took ${ Date.now() - startTime - }ms to complete` + }ms to complete`, ); // Delete the .processing file - fs.unlinkSync(`uploads/720p-${filename}${extension}.processing`); + fs.unlinkSync(progressFile); resolve(); }) .on("error", (e) => { // Ensure to delete the .processing file even on error - if (fs.existsSync(`uploads/720p-${filename}${extension}.processing`)) { - fs.unlinkSync(`uploads/720p-${filename}${extension}.processing`); + if (fs.existsSync(progressFile)) { + fs.unlinkSync(progressFile); } reject(new Error(e)); @@ -188,7 +188,7 @@ export const ffmpegDownscale = ( export const ffmpegConvert = ( path: string, filename: string, - extension: string + extension: string, ) => { const startTime = Date.now(); const outputOptions = [ @@ -217,15 +217,20 @@ export const ffmpegConvert = ( } return new Promise((resolve, reject) => { + const progressFile = `uploads/${filename}${extension}-progress.json`; + ffmpeg() .input(path) .outputOptions(outputOptions) .output("uploads/") .outputFormat(outputFormat) .output(`uploads/${filename}${outputFormat}`) + .on("progress", function(progress) { + fs.writeFileSync(progressFile, JSON.stringify({ progress: progress.percent / 100 })); + }) .on("end", function () { console.log( - `Conversion complete, took ${Date.now() - startTime} to complete` + `Conversion complete, took ${Date.now() - startTime} to complete`, ); console.log(`uploads/${filename}${outputFormat}`); resolve(); @@ -238,9 +243,14 @@ export const ffmpegConvert = ( export const ffProbe = async ( path: string, filename: string, - extension: string + extension: string, ) => { return new Promise((resolve, reject) => { + if (!videoExtensions.includes(extension) && !imageExtensions.includes(extension)) { + console.log(`Extension is ${extension}`); + reject(`Submitted file is neither a video nor an image: ${path}`); + } + ffprobe(path, (err, data) => { if (err) reject(err); resolve(data); diff --git a/app/lib/lib.ts b/app/lib/lib.ts index 6dfafb5..74576ce 100644 --- a/app/lib/lib.ts +++ b/app/lib/lib.ts @@ -25,6 +25,17 @@ export interface User { salt?: any; } +export interface oembedObj { + type: string; + version: string; + provider_name: string; + provider_url: string; + cache_age: number; + html: string; + width?: number; + height?: number; +} + export const videoExtensions = [ ".mp4", ".mov", diff --git a/app/lib/middleware.ts b/app/lib/middleware.ts index 49ac254..b2805b2 100644 --- a/app/lib/middleware.ts +++ b/app/lib/middleware.ts @@ -3,7 +3,7 @@ import type { RequestHandler as Middleware, NextFunction } from "express"; import fs from "fs"; import process from "process"; -import { extension, videoExtensions, imageExtensions } from "./lib"; +import { extension, videoExtensions, imageExtensions, oembedObj } from "./lib"; import { insertToDB } from "./db"; import { ffmpegDownscale, ffProbe } from "./ffmpeg"; import { ffprobe } from "fluent-ffmpeg"; @@ -50,15 +50,9 @@ export const createEmbedData: Middleware = async (req, res, next) => { const files = req.files as Express.Multer.File[]; for (const file in files) { const nameAndExtension = extension(files[file].originalname); - const ffProbeData = await ffProbe( - `uploads/${files[file].originalname}`, - nameAndExtension[0], - nameAndExtension[1], - ); - const width = ffProbeData.streams[0].width; - const height = ffProbeData.streams[0].height; + const isMedia = videoExtensions.includes(nameAndExtension[1]) || imageExtensions.includes(nameAndExtension[1]); - const oembed = { + const oembed: oembedObj = { type: "video", version: "1.0", provider_name: "embedder", @@ -66,11 +60,24 @@ export const createEmbedData: Middleware = async (req, res, next) => { cache_age: 86400, html: ``, - width: width, - height: height, + }${nameAndExtension[1]}'>` }; + if (isMedia) { + let ffProbeData; + try { ffProbeData = await ffProbe( + `uploads/${files[file].originalname}`, + nameAndExtension[0], + nameAndExtension[1], + ); } catch (error) { + console.log(`Error: ${error}`); + console.log(nameAndExtension[1]); + } + + oembed.width = ffProbeData.streams[0].width; + oembed.height = ffProbeData.streams[0].height; + } + fs.writeFile( `uploads/oembed-${nameAndExtension[0]}${nameAndExtension[1]}.json`, JSON.stringify(oembed), @@ -121,23 +128,27 @@ export const convertTo720p: Middleware = (req, res, next) => { }; /**Middleware for handling uploaded files. Inserts it into the database */ -export const handleUpload: Middleware = (req, res, next) => { +export const handleUpload: Middleware = async (req, res, next) => { if (!req.file && !req.files) { console.log("No files were uploaded"); return res.status(400).send("No files were uploaded."); } - const files = req.files ? (req.files as Express.Multer.File[]) : req.file; //Check if a single file was uploaded or multiple - const username = req.user ? req.user.username : "sharex"; //if no username was provided, we can presume that it is sharex + const files = req.files ? (req.files as Express.Multer.File[]) : req.file; + const username = req.user ? req.user.username : "sharex"; const expireDate: Date = req.body.expire ? new Date(Date.now() + req.body.expire * 24 * 60 * 60 * 1000) : null; - if (files instanceof Array) { - for (const file in files) { - insertToDB(files[file].filename, expireDate, username); + try { + if (files instanceof Array) { + await Promise.all(files.map(file => insertToDB(file.filename, expireDate, username))); + } else { + await insertToDB(files.filename, expireDate, username); } - } else insertToDB(files.filename, expireDate, username); - - next(); + next(); + } catch (error) { + console.error("Error in handleUpload:", error); + res.status(500).send("Error processing files."); + } }; diff --git a/app/public/css/app.css b/app/public/css/app.css index 5a72026..da81a13 100644 --- a/app/public/css/app.css +++ b/app/public/css/app.css @@ -21,6 +21,60 @@ line-height: 40px; } +.spinner { + /* Positioning and Sizing */ + width: 100px; + height: 100px; + position: relative; + margin: 50px auto; + /* Centering the spinner */ + + /* Text Styling */ + color: #555; + text-align: center; + font-family: Arial, sans-serif; + font-size: 14px; + padding-top: 80px; + /* Adjust as needed for text position */ + + /* Adding a background to the spinner for better visibility */ + background-color: rgba(255, 255, 255, 0.8); + border-radius: 10px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + } + + /* Keyframes for the spinner animation */ + @keyframes spin { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } + } + + /* Spinner Animation */ + .spinner::before { + content: ''; + box-sizing: border-box; + position: absolute; + top: 50%; + left: 50%; + width: 40px; + /* Spinner Size */ + height: 40px; + margin-top: -20px; + /* Half of height */ + margin-left: -20px; + /* Half of width */ + border-radius: 50%; + border: 2px solid transparent; + border-top-color: #007bff; + /* Spinner Color */ + animation: spin 1s linear infinite; + } + #search { padding: 6px 12px; background: rgb(31, 32, 35); diff --git a/app/public/js/index.js b/app/public/js/index.js index c33ba4b..b64136d 100644 --- a/app/public/js/index.js +++ b/app/public/js/index.js @@ -1,59 +1,74 @@ +/* eslint-disable no-undef */ /* eslint-env browser: true */ +let files; + function copyURI(evt) { evt.preventDefault(); - navigator.clipboard.writeText(absolutePath(evt.target.getAttribute("src"))).then(() => { - /* clipboard successfully set */ - console.log("copied"); - }, () => { - /* clipboard write failed */ - console.log("failed"); - }); + navigator.clipboard + .writeText(absolutePath(evt.target.getAttribute("src"))) + .then( + () => { + /* clipboard successfully set */ + console.log("copied"); + }, + () => { + /* clipboard write failed */ + console.log("failed"); + } + ); } function copyA(evt) { evt.preventDefault(); - navigator.clipboard.writeText(absolutePath(evt.target.getAttribute("href"))).then(() => { - console.log("copied"); - }, () => { - console.log("failed"); - }); + navigator.clipboard + .writeText(absolutePath(evt.target.getAttribute("href"))) + .then( + () => { + console.log("copied"); + }, + () => { + console.log("failed"); + } + ); } function copyPath(evt) { - navigator.clipboard.writeText(absolutePath(evt)).then(() => { - console.log("copied"); - }, () => { - console.log("failed"); - }); + navigator.clipboard.writeText(absolutePath(evt)).then( + () => { + console.log("copied"); + }, + () => { + console.log("failed"); + } + ); } -function absolutePath (href) { +function absolutePath(href) { let link = document.createElement("a"); link.href = href; return link.href; } function extension(string) { - return string.slice((string.lastIndexOf(".") - 2 >>> 0) + 2); + return string.slice(((string.lastIndexOf(".") - 2) >>> 0) + 2); } let dropArea = document.getElementById("dropArea"); -["dragenter", "dragover", "dragleave", "drop"].forEach(eventName => { +["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => { dropArea.addEventListener(eventName, preventDefaults, false); }); -function preventDefaults (e) { +function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } -["dragenter", "dragover"].forEach(eventName => { +["dragenter", "dragover"].forEach((eventName) => { dropArea.addEventListener(eventName, highlight, false); -}) - -;["dragleave", "drop"].forEach(eventName => { +}); +["dragleave", "drop"].forEach((eventName) => { dropArea.addEventListener(eventName, unhighlight, false); }); @@ -100,16 +115,14 @@ function handleFiles(files) { files.forEach(previewFile); } - function previewFile(file) { let reader = new FileReader(); reader.readAsDataURL(file); - reader.onloadend = function() { + reader.onloadend = function () { let img = document.createElement("img"); img.src = reader.result; img.className = "image"; document.getElementById("gallery").appendChild(img); - console.log(document.getElementById("fileupload")); document.getElementById("fileupload").src = img.src; }; } @@ -118,15 +131,20 @@ function uploadFile(file) { let xhr = new XMLHttpRequest(); let formData = new FormData(); let reader = new FileReader(); - + xhr.open("POST", "/", true); - xhr.addEventListener("readystatechange", function(e) { - if (xhr.readyState == 4 && xhr.status == 200) { - location.reload(); - } - else if (xhr.readyState == 4 && xhr.status != 200) { - alert(`Upload failed, error code: ${xhr.status}`) + xhr.addEventListener("readystatechange", function (e) { + if (xhr.readyState == 4) { + if (xhr.status == 200) { + let response = xhr.responseText; + //document.getElementById("embedder-list").innerHTML = response; + htmx.ajax("GET", "/media-list", {target: "#embedder-list", swap: "innerHTML"}); + document.getElementById("gallery").innerHTML = ""; + htmx.process(document.body); + } else { + alert(`Upload failed, error code: ${xhr.status}`); + } } }); @@ -136,7 +154,6 @@ function uploadFile(file) { formData.append("fileupload", file); formData.append("expire", document.getElementById("expire").value); - console.log(formData); xhr.send(formData); } @@ -149,18 +166,27 @@ function openFullSize(imageUrl) { video.src = imageUrl; video.controls = true; - if (extension(imageUrl) == ".jpg" || extension(imageUrl) == ".png" || extension(imageUrl) == ".gif" || extension(imageUrl) == ".jpeg" || extension(imageUrl) == ".webp") { + if ( + extension(imageUrl) == ".jpg" || + extension(imageUrl) == ".png" || + extension(imageUrl) == ".gif" || + extension(imageUrl) == ".jpeg" || + extension(imageUrl) == ".webp" + ) { modal.appendChild(img); - } - else if (extension(imageUrl) == ".mp4" || extension(imageUrl) == ".webm" || extension(imageUrl) == ".mov") { + } else if ( + extension(imageUrl) == ".mp4" || + extension(imageUrl) == ".webm" || + extension(imageUrl) == ".mov" + ) { modal.appendChild(video); } - + // Add the modal to the page document.body.appendChild(modal); - + // Add an event listener to close the modal when the user clicks on it - modal.addEventListener("click", function() { + modal.addEventListener("click", function () { modal.remove(); }); } @@ -172,20 +198,112 @@ searchInput.addEventListener("input", () => { let mediaList = document.querySelectorAll("ul.embedder-list li"); mediaList.forEach((li) => { - if (!li.id.toLowerCase().includes(searchValue)) { //make lowercase to allow case insensitivity + if (!li.id.toLowerCase().includes(searchValue)) { + //make lowercase to allow case insensitivity li.classList.add("hide"); li.classList.remove("show"); - li.addEventListener("animationend", function() { - if (searchInput.value !== "") { - this.style.display = "none"; - } - }, {once: true}); // The {once: true} option automatically removes the event listener after it has been called + li.addEventListener( + "animationend", + function () { + if (searchInput.value !== "") { + this.style.display = "none"; + } + }, + { once: true } + ); // The {once: true} option automatically removes the event listener after it has been called } else { - li.style.display = ""; + li.style.display = ""; li.classList.remove("hide"); - if (searchValue === "" && !li.classList.contains("show")) { + if (searchValue === "" && !li.classList.contains("show")) { li.classList.add("show"); } } }); -}); \ No newline at end of file +}); + +function checkFileAvailability(filePath) { + const checkFile = () => { + console.log(`Checking if ${filePath} is processed...`); + fetch(`/uploads/${filePath}-progress.json`) + .then((response) => { + if (response.ok) { + console.log(`${filePath} still processing`); + return response.json(); + } else { + console.log(`${filePath} finished processing`); + console.log(`/uploads/720p-${filePath}-progress.json finished`); + clearInterval(interval); + createVideoElement(filePath); + return; + } + }) + .then((jsonData) => { + // Handle your JSON data here + console.log(jsonData); + }) + .catch((error) => console.error("Error:", error)); + }; + + checkFile(); + const interval = setInterval(checkFile, 5000); +} + +function createVideoElement(filePath) { + const videoContainer = document.getElementById(`video-${filePath}`); + videoContainer.outerHTML = ` + + `; + videoContainer.style.display = "block"; + document.getElementById(`spinner-${filePath}`).style.display = "none"; +} + +async function updateMediaList() { + try { + const response = await fetch("/media-list"); + if (!response.ok) { + throw new Error("Network response was not ok"); + } + + const data = await response.text(); + + document.getElementById("embedder-list").innerHTML = data; + htmx.process(document.body); + } catch (error) { + console.error("There was a problem with the fetch operation:", error); + } +} + +function refreshMediaList(files) { + files.forEach(file => { + console.log(`Checking ${file.path}...`); + if (videoExtensions.includes(extension(file.path))) { + const progressFileName = `uploads/${file.path}-progress.json`; + console.log(`Fetching ${progressFileName}...`); + checkFileAvailability(file.path); + } else { + console.log(`File ${file.path} is not a video, displaying...`); + } + }); +} + +const videoExtensions = [ + ".mp4", + ".mov", + ".avi", + ".flv", + ".mkv", + ".wmv", + ".webm", +]; +const imageExtensions = [ + ".jpg", + ".jpeg", + ".png", + ".gif", + ".bmp", + ".svg", + ".tiff", + ".webp", +]; \ No newline at end of file diff --git a/app/routes/index.ts b/app/routes/index.ts index 00a74db..5fe2d8f 100644 --- a/app/routes/index.ts +++ b/app/routes/index.ts @@ -12,6 +12,7 @@ import imageProbe from "probe-image-size"; import { ffProbe } from "../lib/ffmpeg"; import fs from "fs"; +import path from "path"; import { extension, videoExtensions } from "../lib/lib"; import { db, MediaRow, getPath, deleteId } from "../lib/db"; @@ -26,6 +27,7 @@ import { const upload = multer({ storage: fileStorage /**, fileFilter: fileFilter**/ }); //maybe make this a env variable? /**Middleware to grab media from media database */ + const fetchMedia: Middleware = (req, res, next) => { const admin: boolean = req.user.username == "admin" ? true : false; /**Check if the user is an admin, if so, show all posts from all users */ @@ -63,12 +65,12 @@ router.get( (req: Request, res: Response) => { res.locals.filter = null; res.render("index", { user: req.user }); - }, + } ); -/*router.get("/media-list", fetchMedia, (req: Request, res: Response) => { - res.render("partials/_fileList"); // Render only the file list partial -});*/ +router.get("/media-list", fetchMedia, (req: Request, res: Response) => { + res.render("partials/_fileList", { user: req.user }); // Render only the file list partial +}); router.get( "/gifv/:file", @@ -89,7 +91,7 @@ router.get( const imageData = ffProbe( `uploads/${req.params.file}`, nameAndExtension[0], - nameAndExtension[1], + nameAndExtension[1] ); width = (await imageData).streams[0].width; @@ -103,7 +105,7 @@ router.get( }); } else { const imageData = await imageProbe( - fs.createReadStream(`uploads/${req.params.file}`), + fs.createReadStream(`uploads/${req.params.file}`) ); return res.render("gifv", { url: url, @@ -112,7 +114,7 @@ router.get( height: imageData.height, }); } - }, + } ); router.post( @@ -123,10 +125,11 @@ router.post( convertTo720p, createEmbedData, handleUpload, + fetchMedia, ], (req: Request, res: Response) => { - res.redirect("/"); - }, + return res.render("partials/_fileList", { user: req.user }); // Render only the file list partial + } ); router.post( @@ -134,40 +137,63 @@ router.post( [checkSharexAuth, upload.single("fileupload"), createEmbedData, handleUpload], (req: Request, res: Response) => { return res.send( - `${req.protocol}://${req.get("host")}/uploads/${req.file.filename}`, + `${req.protocol}://${req.get("host")}/uploads/${req.file.filename}` ); - }, + } ); -router.post( +router.get( "/:id(\\d+)/delete", [checkAuth], - async (req: Request, res: Response) => { - const path: any = await getPath(req.params.id); + async (req: Request, res: Response, next: NextFunction) => { + const filename: any = await getPath(req.params.id); + console.log(filename); + const filePath = path.join(__dirname , "../../uploads/" + filename.path); + const oembed = path.join( + __dirname , "../../uploads/oembed-" + filename.path + ".json" + ); - const nameAndExtension = extension(path.path); - - const filesToDelete = [path.path, "oembed-" + path.path + ".json"]; + const nameAndExtension = extension(filePath); + const filesToDelete = [filePath, oembed]; if ( videoExtensions.includes(nameAndExtension[1]) || nameAndExtension[1] == ".gif" ) { - filesToDelete.push("720p-" + path.path); + filesToDelete.push( + path.join(__dirname , "../../uploads/720p-" + filename.path) + ); } - filesToDelete.forEach((path) => { - fs.unlink(path, async (err) => { - console.log(`Deleting ${path}`); - if (err && err.errno == -4058) { - await deleteId("media", req.params.id); - } - await deleteId("media", req.params.id); - }); - }); + // Wait for all file deletions and database operations to complete + await Promise.all( + filesToDelete.map(async (path) => { + return new Promise((resolve, reject) => { + fs.unlink(path, async (err) => { + console.log(`Deleting ${path}`); + if (err) { + if ([-4058, -2].includes(err.errno)) { + //file not found + console.log("File not found, deleting from database"); + await deleteId("media", req.params.id); + } + console.error(`Error deleting file ${path}:`, err); + reject(err); + return; + } + await deleteId("media", req.params.id); + resolve(); + }); + }); + }) + ); - return res.redirect("/"); + next(); }, + [fetchMedia], + (req: Request, res: Response) => { + return res.render("partials/_fileList", { user: req.user }); + } ); export default router; diff --git a/app/views/index.ejs b/app/views/index.ejs index ab7aaba..56a1d8e 100644 --- a/app/views/index.ejs +++ b/app/views/index.ejs @@ -1,75 +1,87 @@ - - - - Embedder - - - - - - - - - - -
-
-

Embedder

- -
-
-
-

Upload a file, copy paste, or drag n' drop into the dashed region

- -

-
-
-

- Select file expiration date: - -

-

Click the file to copy the url

-
-
-
-
    - <% if (files && files.length > 0) { %> - <%- include('partials/_fileList.ejs',) %> - <% } %> -
-
-
- - - - + + + + + Embedder + + + + + + + + + + + + +
+
+

Embedder

+ +
+
+
+

Upload a file, copy paste, or drag n' drop into the dashed region

+ +

+
+
+

+ Select file expiration date: + +

+

Click the file to copy the url

+
+
+
+
    +
    +
    + + + + + + \ No newline at end of file diff --git a/app/views/partials/_fileList.ejs b/app/views/partials/_fileList.ejs index 63cb381..6744f3d 100644 --- a/app/views/partials/_fileList.ejs +++ b/app/views/partials/_fileList.ejs @@ -7,170 +7,61 @@ const videoExtensions = ['.mp4', '.mov', '.avi', '.flv', '.mkv', '.wmv', '.webm' const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.svg', '.tiff', '.webp']; %> - - - - - <% files.forEach(function(file) { %>
  • -
    -
    - <% if (videoExtensions.includes(extension(file.path))) { %> - -
    Optimizing Video for Sharing...
    +
    + <% if (videoExtensions.includes(extension(file.path))) { %> + +
    Optimizing Video for Sharing...
    - -
    - -
    - <% if(user.username == "admin" && file.username != "admin") { %> - <%= file.username %> -
    - <% } %> - Copy as GIFv -
    -
    - <% } else if (extension(file.path) == ".gif") { %> -
    - -
    - <% if(user.username == "admin" && file.username != "admin") { %> - <%= file.username %> -
    - <% } %> - Copy as GIFv -
    -
    - <% } else if (imageExtensions.includes(extension(file.path))) { %> -
    - + +
    + +
    <% if(user.username == "admin" && file.username != "admin") { %> -
    - <%= file.username %> -
    + <%= file.username %> +
    <% } %> + Copy as GIFv
    - <% } else {%> - -
    -

    <%=extension(file.path)%> file

    +
    + <% } else if (extension(file.path) == ".gif") { %> +
    + +
    <% if(user.username == "admin" && file.username != "admin") { %> -
    - <%= file.username %> -
    + <%= file.username %> +
    <% } %> + Copy as GIFv +
    +
    + <% } else if (imageExtensions.includes(extension(file.path))) { %> +
    + + <% if(user.username == "admin" && file.username != "admin") { %> +
    + <%= file.username %>
    <% } %> - - -
    - -
    -
    + <% } else {%> + +
    +

    <%=extension(file.path)%> file

    + <% if(user.username == "admin" && file.username != "admin") { %> +
    + <%= file.username %> +
    + <% } %> +
    + <% } %> + + + +
  • <% }); %> \ No newline at end of file diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..f92a79b Binary files /dev/null and b/bun.lockb differ diff --git a/docker/bun-Dockerfile b/docker/bun-Dockerfile new file mode 100644 index 0000000..071ed3d --- /dev/null +++ b/docker/bun-Dockerfile @@ -0,0 +1,12 @@ +FROM oven/bun:alpine + +WORKDIR /usr/src/app + +COPY ./tsconfig.json /usr/src/app +COPY ./package*.json /usr/src/app/ +COPY ./bun.lockb /usr/src/app +COPY ./app/ /usr/src/app/app + +RUN bun install + +CMD ["bun", "start"]