mirror of
https://github.com/C4illin/ConvertX.git
synced 2025-11-03 13:33:25 +00:00
Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8674557e42 | ||
|
|
87052ce105 | ||
|
|
98ee26f6e2 | ||
|
|
96e2c88465 | ||
|
|
d55ba218ff | ||
|
|
ae2455e73e | ||
|
|
b9fe32053c | ||
|
|
5cf3d74e03 | ||
|
|
2b92778f37 | ||
|
|
27d4da8941 | ||
|
|
2384e22c22 | ||
|
|
6690caeb1e | ||
|
|
c714ade3e2 | ||
|
|
e9e95c61e9 | ||
|
|
b1e0e68d9c | ||
|
|
5ce3706550 | ||
|
|
57e47e95c0 | ||
|
|
6d6bc6cfdd | ||
|
|
b44eb22e77 | ||
|
|
6edfbaa27d | ||
|
|
d669baeff4 | ||
|
|
ec1a7bc015 | ||
|
|
0805241a19 | ||
|
|
83f041daa2 | ||
|
|
55331a4496 | ||
|
|
b53f07e7a7 | ||
|
|
0eb89ae712 | ||
|
|
7dd153b02c | ||
|
|
6ccafeb3b0 | ||
|
|
b703903b22 | ||
|
|
9e66eab0a2 | ||
|
|
b272bf9504 | ||
|
|
56632f3500 | ||
|
|
2d9d8f8b4f | ||
|
|
65d4e0fbbe | ||
|
|
8182d12ea0 | ||
|
|
1c241d4cad | ||
|
|
874ff6ee00 | ||
|
|
e9f1219ad9 | ||
|
|
4811452aec | ||
|
|
382ebad35a | ||
|
|
85945256e7 | ||
|
|
c504692569 | ||
|
|
64a16036be | ||
|
|
b9f038386f | ||
|
|
945775e52b | ||
|
|
e7f3466736 | ||
|
|
ee80eeb18d | ||
|
|
34c7e0bd25 | ||
|
|
492dbd5617 | ||
|
|
0935bf66ce | ||
|
|
7389e0a059 | ||
|
|
c512b45f91 | ||
|
|
3ae2db5d9b | ||
|
|
0945b40a9c | ||
|
|
20b958e547 | ||
|
|
e7e146c6c9 | ||
|
|
005ad2d66b | ||
|
|
e5c3a8acc4 | ||
|
|
87ecbabd1f | ||
|
|
991c4e4ba8 | ||
|
|
87ccd8b44c | ||
|
|
83e6699ca6 | ||
|
|
c91523c038 | ||
|
|
1f73f036b2 | ||
|
|
1223fabfca | ||
|
|
8a42a39e69 | ||
|
|
22023bad25 | ||
|
|
db2f2d8f0a | ||
|
|
d0fa9ac408 | ||
|
|
776a97289b | ||
|
|
95340dd0eb | ||
|
|
7dcd74cc5f | ||
|
|
c5efac9423 | ||
|
|
cceca9a924 | ||
|
|
4d4c13a8d8 |
@@ -12,17 +12,11 @@ const config = {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
tsconfigRootDir: __dirname,
|
||||
project: [
|
||||
"./tsconfig.json",
|
||||
"./cli/tsconfig.eslint.json", // separate eslint config for the CLI since we want to lint and typecheck differently due to template files
|
||||
"./upgrade/tsconfig.json",
|
||||
"./www/tsconfig.json",
|
||||
],
|
||||
project: ["./tsconfig.json"],
|
||||
},
|
||||
overrides: [
|
||||
// Template files don't have reliable type information
|
||||
{
|
||||
files: ["./cli/template/**/*.{ts,tsx}"],
|
||||
extends: ["plugin:@typescript-eslint/disable-type-checked"],
|
||||
},
|
||||
],
|
||||
@@ -46,9 +40,6 @@ const config = {
|
||||
// These lint rules don't make sense for us but are enabled in the preset configs
|
||||
"@typescript-eslint/no-confusing-void-expression": "off",
|
||||
"@typescript-eslint/restrict-template-expressions": "off",
|
||||
|
||||
// This rule doesn't seem to be working properly
|
||||
"@typescript-eslint/prefer-nullish-coalescing": "off",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
1
.github/workflows/docker-publish.yml
vendored
1
.github/workflows/docker-publish.yml
vendored
@@ -61,6 +61,7 @@ jobs:
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
4
.github/workflows/release-please.yml
vendored
4
.github/workflows/release-please.yml
vendored
@@ -18,8 +18,8 @@ jobs:
|
||||
# this assumes that you have created a personal access token
|
||||
# (PAT) and configured it as a GitHub action secret named
|
||||
# `MY_RELEASE_PLEASE_TOKEN` (this secret name is not important).
|
||||
# token: ${{ secrets.MY_RELEASE_PLEASE_TOKEN }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ secrets.MY_RELEASE_PLEASE_TOKEN }}
|
||||
# token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# this is a built-in strategy in release-please, see "Action Inputs"
|
||||
# for more options
|
||||
release-type: node
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -46,4 +46,5 @@ package-lock.json
|
||||
/output
|
||||
/db
|
||||
/data
|
||||
/Bruno
|
||||
/Bruno
|
||||
/tsconfig.tsbuildinfo
|
||||
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||
}
|
||||
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,5 +1,26 @@
|
||||
# Changelog
|
||||
|
||||
## [0.3.3](https://github.com/C4illin/ConvertX/compare/v0.3.2...v0.3.3) (2024-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* downgrade @elysiajs/html dependency to version 1.0.2 ([c714ade](https://github.com/C4illin/ConvertX/commit/c714ade3e23865ba6cfaf76c9e7259df1cda222c))
|
||||
|
||||
## [0.3.2](https://github.com/C4illin/ConvertX/compare/v0.3.1...v0.3.2) (2024-07-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* increase max request body to support large uploads ([3ae2db5](https://github.com/C4illin/ConvertX/commit/3ae2db5d9b36fe3dcd4372ddcd32aa573ea59aa6)), closes [#64](https://github.com/C4illin/ConvertX/issues/64)
|
||||
|
||||
## [0.3.1](https://github.com/C4illin/ConvertX/compare/v0.3.0...v0.3.1) (2024-06-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* release releases ([4d4c13a](https://github.com/C4illin/ConvertX/commit/4d4c13a8d85ec7c9209ad41cdbea7d4380b0edbf))
|
||||
|
||||
## [0.3.0](https://github.com/C4illin/ConvertX/compare/v0.2.0...v0.3.0) (2024-06-27)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM oven/bun:1-alpine as base
|
||||
FROM oven/bun:1.1.21-alpine as base
|
||||
WORKDIR /app
|
||||
|
||||
# install dependencies into temp directory
|
||||
|
||||
@@ -28,6 +28,8 @@ A self-hosted online file converter. Supports 831 different formats. Written wit
|
||||
|
||||
<!-- many ffmpeg fileformats are duplicates -->
|
||||
|
||||
Any missing converter? Open an issue or pull request!
|
||||
|
||||
## Deployment
|
||||
|
||||
```yml
|
||||
|
||||
42
package.json
42
package.json
@@ -1,40 +1,48 @@
|
||||
{
|
||||
"name": "convertx-frontend",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.3",
|
||||
"scripts": {
|
||||
"dev": "bun run --watch src/index.tsx",
|
||||
"hot": "bun run --hot src/index.tsx",
|
||||
"format": "biome format --write ./src",
|
||||
"css": "cpy 'node_modules/@picocss/pico/css/pico.lime.min.css' 'src/public/' --flat"
|
||||
"css": "cpy 'node_modules/@picocss/pico/css/pico.lime.min.css' 'src/public/' --flat",
|
||||
"lint": "run-p 'lint:*'",
|
||||
"lint:tsc": "tsc --noEmit",
|
||||
"lint:knip": "knip"
|
||||
},
|
||||
"dependencies": {
|
||||
"@elysiajs/cookie": "^0.8.0",
|
||||
"@elysiajs/html": "^1.0.2",
|
||||
"@elysiajs/jwt": "^1.0.2",
|
||||
"@elysiajs/static": "^1.0.3",
|
||||
"elysia": "^1.0.25"
|
||||
"@elysiajs/html": "1.0.2",
|
||||
"@elysiajs/jwt": "^1.1.0",
|
||||
"@elysiajs/static": "1.0.3",
|
||||
"elysia": "^1.1.4"
|
||||
},
|
||||
"module": "src/index.tsx",
|
||||
"type": "module",
|
||||
"bun-create": {
|
||||
"start": "bun run src/index.tsx"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.8.2",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"@kitajs/ts-html-plugin": "^4.0.1",
|
||||
"@biomejs/biome": "1.8.3",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.3.1",
|
||||
"@kitajs/ts-html-plugin": "^4.0.2",
|
||||
"@picocss/pico": "^2.0.6",
|
||||
"@total-typescript/ts-reset": "^0.5.1",
|
||||
"@types/bun": "^1.1.6",
|
||||
"@types/eslint": "^8.56.10",
|
||||
"@types/node": "^20.14.9",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "^7.14.1",
|
||||
"@typescript-eslint/parser": "^7.14.1",
|
||||
"@types/eslint": "^9.6.0",
|
||||
"@types/node": "^22.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"cpy-cli": "^5.0.0",
|
||||
"eslint": "^9.8.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"prettier": "^3.3.2",
|
||||
"typescript": "^5.5.2"
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-isaacscript": "^3.12.2",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"knip": "^5.27.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "^5.5.4"
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"@biomejs/biome"
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
export const BaseHtml = ({ children, title = "ConvertX" }) => (
|
||||
export const BaseHtml = ({
|
||||
children,
|
||||
title = "ConvertX",
|
||||
}: { children: JSX.Element; title?: string }) => (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
@@ -30,7 +30,7 @@ export const Header = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="container">
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
|
||||
@@ -260,6 +260,7 @@ export const properties = {
|
||||
"mpegts",
|
||||
"mpegtsraw",
|
||||
"mpegvideo",
|
||||
"mpg",
|
||||
"mpjpeg",
|
||||
"mpl2",
|
||||
"mpo",
|
||||
|
||||
@@ -201,7 +201,7 @@ for (const converterName in properties) {
|
||||
}
|
||||
possibleInputs.sort();
|
||||
|
||||
export const getPossibleInputs = () => {
|
||||
const getPossibleInputs = () => {
|
||||
return possibleInputs;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
import sharp from "sharp";
|
||||
import type { FormatEnum } from "sharp";
|
||||
|
||||
// declare possible conversions
|
||||
export const properties = {
|
||||
from: {
|
||||
images: [
|
||||
"avif",
|
||||
"bif",
|
||||
"csv",
|
||||
"exr",
|
||||
"fits",
|
||||
"gif",
|
||||
"hdr.gz",
|
||||
"hdr",
|
||||
"heic",
|
||||
"heif",
|
||||
"img.gz",
|
||||
"img",
|
||||
"j2c",
|
||||
"j2k",
|
||||
"jp2",
|
||||
"jpeg",
|
||||
"jpx",
|
||||
"jxl",
|
||||
"mat",
|
||||
"mrxs",
|
||||
"ndpi",
|
||||
"nia.gz",
|
||||
"nia",
|
||||
"nii.gz",
|
||||
"nii",
|
||||
"pdf",
|
||||
"pfm",
|
||||
"pgm",
|
||||
"pic",
|
||||
"png",
|
||||
"ppm",
|
||||
"raw",
|
||||
"scn",
|
||||
"svg",
|
||||
"svs",
|
||||
"svslide",
|
||||
"szi",
|
||||
"tif",
|
||||
"tiff",
|
||||
"v",
|
||||
"vips",
|
||||
"vms",
|
||||
"vmu",
|
||||
"webp",
|
||||
"zip",
|
||||
],
|
||||
},
|
||||
to: {
|
||||
images: [
|
||||
"avif",
|
||||
"dzi",
|
||||
"fits",
|
||||
"gif",
|
||||
"hdr.gz",
|
||||
"heic",
|
||||
"heif",
|
||||
"img.gz",
|
||||
"j2c",
|
||||
"j2k",
|
||||
"jp2",
|
||||
"jpeg",
|
||||
"jpx",
|
||||
"jxl",
|
||||
"mat",
|
||||
"nia.gz",
|
||||
"nia",
|
||||
"nii.gz",
|
||||
"nii",
|
||||
"png",
|
||||
"tiff",
|
||||
"vips",
|
||||
"webp",
|
||||
],
|
||||
},
|
||||
options: {
|
||||
svg: {
|
||||
scale: {
|
||||
description: "Scale the image up or down",
|
||||
type: "number",
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export async function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: keyof FormatEnum,
|
||||
targetPath: string,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
options?: any,
|
||||
) {
|
||||
if (fileType === "svg") {
|
||||
const scale = options.scale || 1;
|
||||
const metadata = await sharp(filePath).metadata();
|
||||
|
||||
if (!metadata || !metadata.width || !metadata.height) {
|
||||
throw new Error("Could not get metadata from image");
|
||||
}
|
||||
|
||||
const newWidth = Math.round(metadata.width * scale);
|
||||
const newHeight = Math.round(metadata.height * scale);
|
||||
|
||||
return await sharp(filePath)
|
||||
.resize(newWidth, newHeight)
|
||||
.toFormat(convertTo)
|
||||
.toFile(targetPath);
|
||||
}
|
||||
|
||||
return await sharp(filePath).toFormat(convertTo).toFile(targetPath);
|
||||
}
|
||||
@@ -72,4 +72,14 @@ if (process.env.NODE_ENV === "production") {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("bun -v", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Bun is not installed. wait what");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`Bun v${stdout.split("\n")[0]}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
588
src/index.tsx
588
src/index.tsx
@@ -21,9 +21,7 @@ import {
|
||||
} from "./helpers/normalizeFiletype";
|
||||
import "./helpers/printVersions";
|
||||
|
||||
|
||||
|
||||
|
||||
mkdir("./data", { recursive: true }).catch(console.error);
|
||||
const db = new Database("./data/mydb.sqlite", { create: true });
|
||||
const uploadsDir = "./data/uploads/";
|
||||
const outputDir = "./data/output/";
|
||||
@@ -78,33 +76,37 @@ if (dbVersion === 0) {
|
||||
|
||||
let FIRST_RUN = db.query("SELECT * FROM users").get() === null || false;
|
||||
|
||||
interface IUser {
|
||||
id: number;
|
||||
email: string;
|
||||
password: string;
|
||||
class User {
|
||||
id!: number;
|
||||
email!: string;
|
||||
password!: string;
|
||||
}
|
||||
|
||||
interface IFileNames {
|
||||
id: number;
|
||||
job_id: number;
|
||||
file_name: string;
|
||||
output_file_name: string;
|
||||
status: string;
|
||||
class Filename {
|
||||
id!: number;
|
||||
job_id!: number;
|
||||
file_name!: string;
|
||||
output_file_name!: string;
|
||||
status!: string;
|
||||
}
|
||||
|
||||
interface IJobs {
|
||||
finished_files: number;
|
||||
id: number;
|
||||
user_id: number;
|
||||
date_created: string;
|
||||
status: string;
|
||||
num_files: number;
|
||||
class Jobs {
|
||||
finished_files!: number;
|
||||
id!: number;
|
||||
user_id!: number;
|
||||
date_created!: string;
|
||||
status!: string;
|
||||
num_files!: number;
|
||||
}
|
||||
|
||||
// enable WAL mode
|
||||
db.exec("PRAGMA journal_mode = WAL;");
|
||||
|
||||
const app = new Elysia()
|
||||
const app = new Elysia({
|
||||
serve: {
|
||||
maxRequestBodySize: Number.MAX_SAFE_INTEGER,
|
||||
},
|
||||
})
|
||||
.use(cookie())
|
||||
.use(html())
|
||||
.use(
|
||||
@@ -173,36 +175,38 @@ const app = new Elysia()
|
||||
|
||||
return (
|
||||
<BaseHtml title="ConvertX | Register">
|
||||
<Header accountRegistration={ACCOUNT_REGISTRATION} />
|
||||
<main class="container">
|
||||
<article>
|
||||
<form method="post">
|
||||
<fieldset>
|
||||
<label>
|
||||
Email
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="Email"
|
||||
autocomplete="email"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Password
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
autocomplete="new-password"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
<input type="submit" value="Register" />
|
||||
</form>
|
||||
</article>
|
||||
</main>
|
||||
<>
|
||||
<Header accountRegistration={ACCOUNT_REGISTRATION} />
|
||||
<main class="container">
|
||||
<article>
|
||||
<form method="post">
|
||||
<fieldset>
|
||||
<label>
|
||||
Email
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="Email"
|
||||
autocomplete="email"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Password
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
autocomplete="new-password"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
<input type="submit" value="Register" />
|
||||
</form>
|
||||
</article>
|
||||
</main>
|
||||
</>
|
||||
</BaseHtml>
|
||||
);
|
||||
})
|
||||
@@ -233,9 +237,17 @@ const app = new Elysia()
|
||||
savedPassword,
|
||||
);
|
||||
|
||||
const user = (await db
|
||||
const user = db
|
||||
.query("SELECT * FROM users WHERE email = ?")
|
||||
.get(body.email)) as IUser;
|
||||
.as(User)
|
||||
.get(body.email);
|
||||
|
||||
if (!user) {
|
||||
set.status = 500;
|
||||
return {
|
||||
message: "Failed to create user.",
|
||||
};
|
||||
}
|
||||
|
||||
const accessToken = await jwt.sign({
|
||||
id: String(user.id),
|
||||
@@ -279,52 +291,55 @@ const app = new Elysia()
|
||||
|
||||
return (
|
||||
<BaseHtml title="ConvertX | Login">
|
||||
<Header accountRegistration={ACCOUNT_REGISTRATION} />
|
||||
<main class="container">
|
||||
<article>
|
||||
<form method="post">
|
||||
<fieldset>
|
||||
<label>
|
||||
Email
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="Email"
|
||||
autocomplete="email"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Password
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
<div role="group">
|
||||
{ACCOUNT_REGISTRATION && (
|
||||
<a href="/register" role="button" class="secondary">
|
||||
Register an account
|
||||
</a>
|
||||
)}
|
||||
<input type="submit" value="Login" />
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
</main>
|
||||
<>
|
||||
<Header accountRegistration={ACCOUNT_REGISTRATION} />
|
||||
<main class="container">
|
||||
<article>
|
||||
<form method="post">
|
||||
<fieldset>
|
||||
<label>
|
||||
Email
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="Email"
|
||||
autocomplete="email"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Password
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
<div role="group">
|
||||
{ACCOUNT_REGISTRATION && (
|
||||
<a href="/register" role="button" class="secondary">
|
||||
Register an account
|
||||
</a>
|
||||
)}
|
||||
<input type="submit" value="Login" />
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
</main>
|
||||
</>
|
||||
</BaseHtml>
|
||||
);
|
||||
})
|
||||
.post(
|
||||
"/login",
|
||||
async function handler({ body, set, redirect, jwt, cookie: { auth } }) {
|
||||
const existingUser = (await db
|
||||
const existingUser = await db
|
||||
.query("SELECT * FROM users WHERE email = ?")
|
||||
.get(body.email)) as IUser;
|
||||
.as(User)
|
||||
.get(body.email);
|
||||
|
||||
if (!existingUser) {
|
||||
set.status = 403;
|
||||
@@ -398,9 +413,10 @@ const app = new Elysia()
|
||||
}
|
||||
|
||||
// make sure user exists in db
|
||||
const existingUser = (await db
|
||||
const existingUser = await db
|
||||
.query("SELECT * FROM users WHERE id = ?")
|
||||
.get(user.id)) as IUser;
|
||||
.as(User)
|
||||
.get(user.id);
|
||||
|
||||
if (!existingUser) {
|
||||
if (auth?.value) {
|
||||
@@ -437,16 +453,17 @@ const app = new Elysia()
|
||||
|
||||
return (
|
||||
<BaseHtml>
|
||||
<Header loggedIn />
|
||||
<main class="container">
|
||||
<article>
|
||||
<h1>Convert</h1>
|
||||
<div style={{ maxHeight: "50vh", overflowY: "auto" }}>
|
||||
<table id="file-list" class="striped" />
|
||||
</div>
|
||||
<input type="file" name="file" multiple />
|
||||
{/* <label for="convert_from">Convert from</label> */}
|
||||
{/* <select name="convert_from" aria-label="Convert from" required>
|
||||
<>
|
||||
<Header loggedIn />
|
||||
<main class="container">
|
||||
<article>
|
||||
<h1>Convert</h1>
|
||||
<div style={{ maxHeight: "50vh", overflowY: "auto" }}>
|
||||
<table id="file-list" class="striped" />
|
||||
</div>
|
||||
<input type="file" name="file" multiple />
|
||||
{/* <label for="convert_from">Convert from</label> */}
|
||||
{/* <select name="convert_from" aria-label="Convert from" required>
|
||||
<option selected disabled value="">
|
||||
Convert from
|
||||
</option>
|
||||
@@ -455,31 +472,34 @@ const app = new Elysia()
|
||||
<option>{input}</option>
|
||||
))}
|
||||
</select> */}
|
||||
</article>
|
||||
<form method="post" action="/convert">
|
||||
<input type="hidden" name="file_names" id="file_names" />
|
||||
<article>
|
||||
<select name="convert_to" aria-label="Convert to" required>
|
||||
<option selected disabled value="">
|
||||
Convert to
|
||||
</option>
|
||||
{Object.entries(getAllTargets()).map(([converter, targets]) => (
|
||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
||||
<optgroup label={converter}>
|
||||
{targets.map((target) => (
|
||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
||||
<option value={`${target},${converter}`} safe>
|
||||
{target}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
))}
|
||||
</select>
|
||||
</article>
|
||||
<input type="submit" value="Convert" />
|
||||
</form>
|
||||
</main>
|
||||
<script src="script.js" defer />
|
||||
<form method="post" action="/convert">
|
||||
<input type="hidden" name="file_names" id="file_names" />
|
||||
<article>
|
||||
<select name="convert_to" aria-label="Convert to" required>
|
||||
<option selected disabled value="">
|
||||
Convert to
|
||||
</option>
|
||||
{Object.entries(getAllTargets()).map(
|
||||
([converter, targets]) => (
|
||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
||||
<optgroup label={converter}>
|
||||
{targets.map((target) => (
|
||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
||||
<option value={`${target},${converter}`} safe>
|
||||
{target}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
),
|
||||
)}
|
||||
</select>
|
||||
</article>
|
||||
<input type="submit" value="Convert" />
|
||||
</form>
|
||||
</main>
|
||||
<script src="script.js" defer />
|
||||
</>
|
||||
</BaseHtml>
|
||||
);
|
||||
})
|
||||
@@ -603,9 +623,10 @@ const app = new Elysia()
|
||||
return redirect("/", 302);
|
||||
}
|
||||
|
||||
const existingJob = (await db
|
||||
const existingJob = await db
|
||||
.query("SELECT * FROM jobs WHERE id = ? AND user_id = ?")
|
||||
.get(jobId.value, user.id)) as IJobs;
|
||||
.as(Jobs)
|
||||
.get(jobId.value, user.id);
|
||||
|
||||
if (!existingJob) {
|
||||
return redirect("/", 302);
|
||||
@@ -634,14 +655,12 @@ const app = new Elysia()
|
||||
return redirect("/", 302);
|
||||
}
|
||||
|
||||
db.run(
|
||||
"UPDATE jobs SET num_files = ?, status = 'pending' WHERE id = ?",
|
||||
fileNames.length,
|
||||
jobId.value,
|
||||
);
|
||||
db.query(
|
||||
"UPDATE jobs SET num_files = ?1, status = 'pending' WHERE id = ?2",
|
||||
).run(fileNames.length, jobId.value);
|
||||
|
||||
const query = db.query(
|
||||
"INSERT INTO file_names (job_id, file_name, output_file_name, status) VALUES (?, ?, ?, ?)",
|
||||
"INSERT INTO file_names (job_id, file_name, output_file_name, status) VALUES (?1, ?2, ?3, ?4)",
|
||||
);
|
||||
|
||||
// Start the conversion process in the background
|
||||
@@ -662,16 +681,18 @@ const app = new Elysia()
|
||||
{},
|
||||
converterName,
|
||||
);
|
||||
|
||||
query.run(jobId.value, fileName, newFileName, result);
|
||||
if (jobId.value) {
|
||||
query.run(jobId.value, fileName, newFileName, result);
|
||||
}
|
||||
}),
|
||||
)
|
||||
.then(() => {
|
||||
// All conversions are done, update the job status to 'completed'
|
||||
db.run(
|
||||
"UPDATE jobs SET status = 'completed' WHERE id = ?",
|
||||
jobId.value,
|
||||
);
|
||||
if (jobId.value) {
|
||||
db.query("UPDATE jobs SET status = 'completed' WHERE id = ?1").run(
|
||||
jobId.value,
|
||||
);
|
||||
}
|
||||
|
||||
// delete all uploaded files in userUploadsDir
|
||||
// rmSync(userUploadsDir, { recursive: true, force: true });
|
||||
@@ -702,12 +723,14 @@ const app = new Elysia()
|
||||
|
||||
let userJobs = db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ?")
|
||||
.all(user.id) as IJobs[];
|
||||
.as(Jobs)
|
||||
.all(user.id);
|
||||
|
||||
for (const job of userJobs) {
|
||||
const files = db
|
||||
.query("SELECT * FROM file_names WHERE job_id = ?")
|
||||
.all(job.id) as IFileNames[];
|
||||
.as(Filename)
|
||||
.all(job.id);
|
||||
|
||||
job.finished_files = files.length;
|
||||
}
|
||||
@@ -717,37 +740,39 @@ const app = new Elysia()
|
||||
|
||||
return (
|
||||
<BaseHtml title="ConvertX | Results">
|
||||
<Header loggedIn />
|
||||
<main class="container">
|
||||
<article>
|
||||
<h1>Results</h1>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Files</th>
|
||||
<th>Files Done</th>
|
||||
<th>Status</th>
|
||||
<th>View</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{userJobs.map((job) => (
|
||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
||||
<>
|
||||
<Header loggedIn />
|
||||
<main class="container">
|
||||
<article>
|
||||
<h1>Results</h1>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td safe>{job.date_created}</td>
|
||||
<td>{job.num_files}</td>
|
||||
<td>{job.finished_files}</td>
|
||||
<td safe>{job.status}</td>
|
||||
<td>
|
||||
<a href={`/results/${job.id}`}>View</a>
|
||||
</td>
|
||||
<th>Time</th>
|
||||
<th>Files</th>
|
||||
<th>Files Done</th>
|
||||
<th>Status</th>
|
||||
<th>View</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</main>
|
||||
</thead>
|
||||
<tbody>
|
||||
{userJobs.map((job) => (
|
||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
||||
<tr>
|
||||
<td safe>{job.date_created}</td>
|
||||
<td>{job.num_files}</td>
|
||||
<td>{job.finished_files}</td>
|
||||
<td safe>{job.status}</td>
|
||||
<td>
|
||||
<a href={`/results/${job.id}`}>View</a>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</main>
|
||||
</>
|
||||
</BaseHtml>
|
||||
);
|
||||
})
|
||||
@@ -768,9 +793,10 @@ const app = new Elysia()
|
||||
return redirect("/login", 302);
|
||||
}
|
||||
|
||||
const job = (await db
|
||||
const job = await db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||
.get(user.id, params.jobId)) as IJobs;
|
||||
.as(Jobs)
|
||||
.get(user.id, params.jobId);
|
||||
|
||||
if (!job) {
|
||||
set.status = 404;
|
||||
@@ -783,65 +809,68 @@ const app = new Elysia()
|
||||
|
||||
const files = db
|
||||
.query("SELECT * FROM file_names WHERE job_id = ?")
|
||||
.all(params.jobId) as IFileNames[];
|
||||
.as(Filename)
|
||||
.all(params.jobId);
|
||||
|
||||
return (
|
||||
<BaseHtml title="ConvertX | Result">
|
||||
<Header loggedIn />
|
||||
<main class="container">
|
||||
<article>
|
||||
<div class="grid">
|
||||
<h1>Results</h1>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
style={{ width: "10rem", float: "right" }}
|
||||
onclick="downloadAll()"
|
||||
{...(files.length !== job.num_files
|
||||
? { disabled: true, "aria-busy": "true" }
|
||||
: "")}>
|
||||
{files.length === job.num_files
|
||||
? "Download All"
|
||||
: "Converting..."}
|
||||
</button>
|
||||
<>
|
||||
<Header loggedIn />
|
||||
<main class="container">
|
||||
<article>
|
||||
<div class="grid">
|
||||
<h1>Results</h1>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
style={{ width: "10rem", float: "right" }}
|
||||
onclick="downloadAll()"
|
||||
{...(files.length !== job.num_files
|
||||
? { disabled: true, "aria-busy": "true" }
|
||||
: "")}>
|
||||
{files.length === job.num_files
|
||||
? "Download All"
|
||||
: "Converting..."}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<progress max={job.num_files} value={files.length} />
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Converted File Name</th>
|
||||
<th>Status</th>
|
||||
<th>View</th>
|
||||
<th>Download</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{files.map((file) => (
|
||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
||||
<progress max={job.num_files} value={files.length} />
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td safe>{file.output_file_name}</td>
|
||||
<td safe>{file.status}</td>
|
||||
<td>
|
||||
<a
|
||||
href={`/download/${outputPath}${file.output_file_name}`}>
|
||||
View
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
href={`/download/${outputPath}${file.output_file_name}`}
|
||||
download={file.output_file_name}>
|
||||
Download
|
||||
</a>
|
||||
</td>
|
||||
<th>Converted File Name</th>
|
||||
<th>Status</th>
|
||||
<th>View</th>
|
||||
<th>Download</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</main>
|
||||
<script src="/results.js" defer />
|
||||
</thead>
|
||||
<tbody>
|
||||
{files.map((file) => (
|
||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
||||
<tr>
|
||||
<td safe>{file.output_file_name}</td>
|
||||
<td safe>{file.status}</td>
|
||||
<td>
|
||||
<a
|
||||
href={`/download/${outputPath}${file.output_file_name}`}>
|
||||
View
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
href={`/download/${outputPath}${file.output_file_name}`}
|
||||
download={file.output_file_name}>
|
||||
Download
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</main>
|
||||
<script src="/results.js" defer />
|
||||
</>
|
||||
</BaseHtml>
|
||||
);
|
||||
},
|
||||
@@ -863,9 +892,10 @@ const app = new Elysia()
|
||||
return redirect("/login", 302);
|
||||
}
|
||||
|
||||
const job = (await db
|
||||
const job = await db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||
.get(user.id, params.jobId)) as IJobs;
|
||||
.as(Jobs)
|
||||
.get(user.id, params.jobId);
|
||||
|
||||
if (!job) {
|
||||
set.status = 404;
|
||||
@@ -878,7 +908,8 @@ const app = new Elysia()
|
||||
|
||||
const files = db
|
||||
.query("SELECT * FROM file_names WHERE job_id = ?")
|
||||
.all(params.jobId) as IFileNames[];
|
||||
.as(Filename)
|
||||
.all(params.jobId);
|
||||
|
||||
return (
|
||||
<article>
|
||||
@@ -974,50 +1005,54 @@ const app = new Elysia()
|
||||
|
||||
return (
|
||||
<BaseHtml title="ConvertX | Converters">
|
||||
<Header loggedIn />
|
||||
<main class="container">
|
||||
<article>
|
||||
<h1>Converters</h1>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Converter</th>
|
||||
<th>From (Count)</th>
|
||||
<th>To (Count)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.entries(getAllTargets()).map(([converter, targets]) => {
|
||||
const inputs = getAllInputs(converter);
|
||||
return (
|
||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
||||
<tr>
|
||||
<td safe>{converter}</td>
|
||||
<td>
|
||||
Count: {inputs.length}
|
||||
<ul>
|
||||
{inputs.map((input) => (
|
||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
||||
<li safe>{input}</li>
|
||||
))}
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
Count: {targets.length}
|
||||
<ul>
|
||||
{targets.map((target) => (
|
||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
||||
<li safe>{target}</li>
|
||||
))}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</main>
|
||||
<>
|
||||
<Header loggedIn />
|
||||
<main class="container">
|
||||
<article>
|
||||
<h1>Converters</h1>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Converter</th>
|
||||
<th>From (Count)</th>
|
||||
<th>To (Count)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.entries(getAllTargets()).map(
|
||||
([converter, targets]) => {
|
||||
const inputs = getAllInputs(converter);
|
||||
return (
|
||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
||||
<tr>
|
||||
<td safe>{converter}</td>
|
||||
<td>
|
||||
Count: {inputs.length}
|
||||
<ul>
|
||||
{inputs.map((input) => (
|
||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
||||
<li safe>{input}</li>
|
||||
))}
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
Count: {targets.length}
|
||||
<ul>
|
||||
{targets.map((target) => (
|
||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
||||
<li safe>{target}</li>
|
||||
))}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</main>
|
||||
</>
|
||||
</BaseHtml>
|
||||
);
|
||||
})
|
||||
@@ -1064,7 +1099,8 @@ const clearJobs = () => {
|
||||
// get all files older than 24 hours
|
||||
const jobs = db
|
||||
.query("SELECT * FROM jobs WHERE date_created < ?")
|
||||
.all(new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()) as IJobs[];
|
||||
.as(Jobs)
|
||||
.all(new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString());
|
||||
|
||||
for (const job of jobs) {
|
||||
// delete the directories
|
||||
|
||||
@@ -17,9 +17,6 @@
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"allowJs": true,
|
||||
"types": [
|
||||
"bun-types" // add Bun global
|
||||
],
|
||||
// non bun init
|
||||
"plugins": [{ "name": "@kitajs/ts-html-plugin" }],
|
||||
"noUncheckedIndexedAccess": true,
|
||||
|
||||
Reference in New Issue
Block a user