htmx is so cool

This commit is contained in:
waveringana 2023-11-29 00:30:15 -05:00
parent e04ef78a42
commit 3ed7b0b5c7
11 changed files with 557 additions and 400 deletions

View file

@ -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<void> {
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
}
id?: number | string;
username: string;
hashed_password: any;
salt: any;
}

View file

@ -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<void>((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<void>((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<FfprobeData>((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);

View file

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

View file

@ -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: `<iframe src='${req.protocol}://${req.get("host")}/gifv/${
nameAndExtension[0]
}${nameAndExtension[1]}'></iframe>`,
width: width,
height: height,
}${nameAndExtension[1]}'></iframe>`
};
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.");
}
};