chore: format files

This commit is contained in:
C4illin 2025-08-07 23:28:02 +02:00 committed by Emrik Östling
parent 858ee28ef2
commit 81f109f830
15 changed files with 77 additions and 63 deletions

View file

@ -1,10 +1,9 @@
--- ---
name: Bug report name: Bug report
about: Create a report to help us improve about: Create a report to help us improve
title: '' title: ""
labels: bug labels: bug
assignees: '' assignees: ""
--- ---
**Describe the bug** **Describe the bug**
@ -12,10 +11,12 @@ A clear and concise description of what the bug is.
**To Reproduce** **To Reproduce**
Steps to reproduce the behavior: Steps to reproduce the behavior:
1. Go to '...' 1. Go to '...'
2. Click on '....' 2. Click on '....'
3. Scroll down to '....' 3. Scroll down to '....'
4. See error 4. See error
**Checklist:** **Checklist:**
- [ ] I am accessing ConvertX over HTTPS or have `HTTP_ALLOWED=true` - [ ] I am accessing ConvertX over HTTPS or have `HTTP_ALLOWED=true`

View file

@ -3,20 +3,24 @@ name: Converter request
about: Suggest an converter for this project about: Suggest an converter for this project
title: "[Converter Request]" title: "[Converter Request]"
labels: "converter request" labels: "converter request"
assignees: '' assignees: ""
--- ---
**What file formats is missing?** **What file formats is missing?**
<!-- Provide an example of what you would like to convert --> <!-- Provide an example of what you would like to convert -->
**What converter should be added** **What converter should be added**
<!-- It has to be free and preferably open source --> <!-- It has to be free and preferably open source -->
**Are you willing to add it?** **Are you willing to add it?**
<!-- Adding a converter is very easy just copy one of the existing and modify it --> <!-- Adding a converter is very easy just copy one of the existing and modify it -->
- [ ] Yes - [ ] Yes
- [ ] No - [ ] No
**Additional context** **Additional context**
<!-- Add any other context or screenshots about the feature request here. -->
<!-- Add any other context or screenshots about the feature request here. -->

View file

@ -3,8 +3,7 @@ name: Feature request
about: Suggest an idea for this project about: Suggest an idea for this project
title: "[Feature Request]" title: "[Feature Request]"
labels: enhancement labels: enhancement
assignees: '' assignees: ""
--- ---
**Describe the solution you'd like** **Describe the solution you'd like**

View file

@ -2,10 +2,9 @@
## [0.14.1](https://github.com/C4illin/ConvertX/compare/v0.14.0...v0.14.1) (2025-06-04) ## [0.14.1](https://github.com/C4illin/ConvertX/compare/v0.14.0...v0.14.1) (2025-06-04)
### Bug Fixes ### Bug Fixes
* change to baseline build ([6ea3058](https://github.com/C4illin/ConvertX/commit/6ea3058e66262f7a14633bddcecd5573948f524a)), closes [#311](https://github.com/C4illin/ConvertX/issues/311) - change to baseline build ([6ea3058](https://github.com/C4illin/ConvertX/commit/6ea3058e66262f7a14633bddcecd5573948f524a)), closes [#311](https://github.com/C4illin/ConvertX/issues/311)
## [0.14.0](https://github.com/C4illin/ConvertX/compare/v0.13.0...v0.14.0) (2025-06-03) ## [0.14.0](https://github.com/C4illin/ConvertX/compare/v0.13.0...v0.14.0) (2025-06-03)

View file

@ -94,7 +94,6 @@ All are optional, JWT_SECRET is recommended to be set.
| LANGUAGE | en | Language to format date strings in, specified as a [BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag) | | LANGUAGE | en | Language to format date strings in, specified as a [BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag) |
| UNAUTHENTICATED_USER_SHARING | false | Shares conversion history between all unauthenticated users | | UNAUTHENTICATED_USER_SHARING | false | Shares conversion history between all unauthenticated users |
### Docker images ### Docker images
There is a `:latest` tag that is updated with every release and a `:main` tag that is updated with every push to the main branch. `:latest` is recommended for normal use. There is a `:latest` tag that is updated with every release and a `:main` tag that is updated with every push to the main branch. `:latest` is recommended for normal use.

View file

@ -5,8 +5,5 @@
"enabled": true, "enabled": true,
"automerge": true "automerge": true
}, },
"ignoreDeps": [ "ignoreDeps": ["bun-types", "@types/bun"]
"bun-types",
"@types/bun"
]
} }

View file

@ -66,24 +66,20 @@ export async function convert(
options?: unknown, options?: unknown,
): Promise<string> { ): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
execFile( execFile("ebook-convert", [filePath, targetPath], (error, stdout, stderr) => {
"ebook-convert", if (error) {
[filePath, targetPath], reject(`error: ${error}`);
(error, stdout, stderr) => { }
if (error) {
reject(`error: ${error}`);
}
if (stdout) { if (stdout) {
console.log(`stdout: ${stdout}`); console.log(`stdout: ${stdout}`);
} }
if (stderr) { if (stderr) {
console.error(`stderr: ${stderr}`); console.error(`stderr: ${stderr}`);
} }
resolve("Done"); resolve("Done");
}, });
);
}); });
} }

View file

@ -1,3 +1,4 @@
import { Cookie } from "elysia";
import db from "../db/db"; import db from "../db/db";
import { MAX_CONVERT_PROCESS } from "../helpers/env"; import { MAX_CONVERT_PROCESS } from "../helpers/env";
import { normalizeFiletype, normalizeOutputFiletype } from "../helpers/normalizeFiletype"; import { normalizeFiletype, normalizeOutputFiletype } from "../helpers/normalizeFiletype";
@ -119,11 +120,11 @@ const properties: Record<
}; };
function chunks<T>(arr: T[], size: number): T[][] { function chunks<T>(arr: T[], size: number): T[][] {
if(size <= 0){ if (size <= 0) {
return [arr] return [arr];
} }
return Array.from({ length: Math.ceil(arr.length / size) }, (_: T, i: number) => return Array.from({ length: Math.ceil(arr.length / size) }, (_: T, i: number) =>
arr.slice(i * size, i * size + size) arr.slice(i * size, i * size + size),
); );
} }
@ -133,17 +134,15 @@ export async function handleConvert(
userOutputDir: string, userOutputDir: string,
convertTo: string, convertTo: string,
converterName: string, converterName: string,
jobId: any jobId: Cookie<string | undefined>,
) { ) {
const query = db.query( const query = db.query(
"INSERT INTO file_names (job_id, file_name, output_file_name, status) VALUES (?1, ?2, ?3, ?4)", "INSERT INTO file_names (job_id, file_name, output_file_name, status) VALUES (?1, ?2, ?3, ?4)",
); );
for (const chunk of chunks(fileNames, MAX_CONVERT_PROCESS)) { for (const chunk of chunks(fileNames, MAX_CONVERT_PROCESS)) {
const toProcess: Promise<string>[] = []; const toProcess: Promise<string>[] = [];
for(const fileName of chunk) { for (const fileName of chunk) {
const filePath = `${userUploadsDir}${fileName}`; const filePath = `${userUploadsDir}${fileName}`;
const fileTypeOrig = fileName.split(".").pop() ?? ""; const fileTypeOrig = fileName.split(".").pop() ?? "";
const fileType = normalizeFiletype(fileTypeOrig); const fileType = normalizeFiletype(fileTypeOrig);
@ -154,21 +153,16 @@ export async function handleConvert(
); );
const targetPath = `${userOutputDir}${newFileName}`; const targetPath = `${userOutputDir}${newFileName}`;
toProcess.push( toProcess.push(
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
mainConverter( mainConverter(filePath, fileType, convertTo, targetPath, {}, converterName)
filePath, .then((r) => {
fileType,
convertTo,
targetPath,
{},
converterName,
).then(r => {
if (jobId.value) { if (jobId.value) {
query.run(jobId.value, fileName, newFileName, r); query.run(jobId.value, fileName, newFileName, r);
} }
resolve(r); resolve(r);
}).catch(c => reject(c)); })
}) .catch((c) => reject(c));
}),
); );
} }
await Promise.all(toProcess); await Promise.all(toProcess);

View file

@ -23,7 +23,7 @@ export function convert(
// msgconvert will output to the same directory as the input file with .eml extension // msgconvert will output to the same directory as the input file with .eml extension
// We need to use --outfile to specify the target path // We need to use --outfile to specify the target path
const args = ["--outfile", targetPath, filePath]; const args = ["--outfile", targetPath, filePath];
execFile("msgconvert", args, (error, stdout, stderr) => { execFile("msgconvert", args, (error, stdout, stderr) => {
if (error) { if (error) {
reject(new Error(`msgconvert failed: ${error.message}`)); reject(new Error(`msgconvert failed: ${error.message}`));
@ -33,13 +33,19 @@ export function convert(
if (stderr) { if (stderr) {
// Log sanitized stderr to avoid exposing sensitive paths // Log sanitized stderr to avoid exposing sensitive paths
const sanitizedStderr = stderr.replace(/(\/[^\s]+)/g, "[REDACTED_PATH]"); const sanitizedStderr = stderr.replace(/(\/[^\s]+)/g, "[REDACTED_PATH]");
console.warn(`msgconvert stderr: ${sanitizedStderr.length > 200 ? sanitizedStderr.slice(0, 200) + '...' : sanitizedStderr}`); console.warn(
`msgconvert stderr: ${sanitizedStderr.length > 200 ? sanitizedStderr.slice(0, 200) + "..." : sanitizedStderr}`,
);
} }
resolve(targetPath); resolve(targetPath);
}); });
} else { } else {
reject(new Error(`Unsupported conversion from ${fileType} to ${convertTo}. Only MSG to EML conversion is currently supported.`)); reject(
new Error(
`Unsupported conversion from ${fileType} to ${convertTo}. Only MSG to EML conversion is currently supported.`,
),
);
} }
}); });
} }

View file

@ -16,7 +16,10 @@ export const WEBROOT = process.env.WEBROOT ?? "";
export const LANGUAGE = process.env.LANGUAGE?.toLowerCase() || "en"; export const LANGUAGE = process.env.LANGUAGE?.toLowerCase() || "en";
export const MAX_CONVERT_PROCESS = process.env.MAX_CONVERT_PROCESS && Number(process.env.MAX_CONVERT_PROCESS) > 0 ? Number(process.env.MAX_CONVERT_PROCESS) : 0 export const MAX_CONVERT_PROCESS =
process.env.MAX_CONVERT_PROCESS && Number(process.env.MAX_CONVERT_PROCESS) > 0
? Number(process.env.MAX_CONVERT_PROCESS)
: 0;
export const UNAUTHENTICATED_USER_SHARING = export const UNAUTHENTICATED_USER_SHARING =
process.env.UNAUTHENTICATED_USER_SHARING?.toLowerCase() === "true" || false; process.env.UNAUTHENTICATED_USER_SHARING?.toLowerCase() === "true" || false;

View file

@ -46,6 +46,11 @@ export const convert = new Elysia().use(userService).post(
const convertTo = normalizeFiletype(body.convert_to.split(",")[0] ?? ""); const convertTo = normalizeFiletype(body.convert_to.split(",")[0] ?? "");
const converterName = body.convert_to.split(",")[1]; const converterName = body.convert_to.split(",")[1];
if (!converterName) {
return redirect(`${WEBROOT}/`, 302);
}
const fileNames = JSON.parse(body.file_names) as string[]; const fileNames = JSON.parse(body.file_names) as string[];
for (let i = 0; i < fileNames.length; i++) { for (let i = 0; i < fileNames.length; i++) {

View file

@ -1,11 +1,11 @@
import path from "node:path";
import { Elysia } from "elysia"; import { Elysia } from "elysia";
import sanitize from "sanitize-filename"; import sanitize from "sanitize-filename";
import * as tar from "tar";
import { outputDir } from ".."; import { outputDir } from "..";
import db from "../db/db"; import db from "../db/db";
import { WEBROOT } from "../helpers/env"; import { WEBROOT } from "../helpers/env";
import { userService } from "./user"; import { userService } from "./user";
import path from "node:path";
import * as tar from "tar";
export const download = new Elysia() export const download = new Elysia()
.use(userService) .use(userService)
@ -58,8 +58,17 @@ export const download = new Elysia()
const userId = decodeURIComponent(params.userId); const userId = decodeURIComponent(params.userId);
const jobId = decodeURIComponent(params.jobId); const jobId = decodeURIComponent(params.jobId);
const outputPath = `${outputDir}${userId}/${jobId}`; const outputPath = `${outputDir}${userId}/${jobId}`;
const outputTar = path.join(outputPath, `converted_files_${jobId}.tar`) const outputTar = path.join(outputPath, `converted_files_${jobId}.tar`);
await tar.create({file: outputTar, cwd: outputPath, filter: (path) => { return !path.match(".*\\.tar"); }}, ["."]); await tar.create(
{
file: outputTar,
cwd: outputPath,
filter: (path) => {
return !path.match(".*\\.tar");
},
},
["."],
);
return Bun.file(outputTar); return Bun.file(outputTar);
}); });

View file

@ -1,4 +1,5 @@
import { Html } from "@elysiajs/html"; import { Html } from "@elysiajs/html";
import { JWTPayloadSpec } from "@elysiajs/jwt";
import { Elysia } from "elysia"; import { Elysia } from "elysia";
import { BaseHtml } from "../components/base"; import { BaseHtml } from "../components/base";
import { Header } from "../components/header"; import { Header } from "../components/header";
@ -6,7 +7,6 @@ import db from "../db/db";
import { Filename, Jobs } from "../db/types"; import { Filename, Jobs } from "../db/types";
import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env"; import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env";
import { userService } from "./user"; import { userService } from "./user";
import { JWTPayloadSpec } from "@elysiajs/jwt";
function ResultsArticle({ function ResultsArticle({
user, user,
@ -26,7 +26,7 @@ function ResultsArticle({
<div class="mb-4 flex items-center justify-between"> <div class="mb-4 flex items-center justify-between">
<h1 class="text-xl">Results</h1> <h1 class="text-xl">Results</h1>
<div> <div>
<a <a
style={files.length !== job.num_files ? "pointer-events: none;" : ""} style={files.length !== job.num_files ? "pointer-events: none;" : ""}
href={`${WEBROOT}/archive/${user.id}/${job.id}`} href={`${WEBROOT}/archive/${user.id}/${job.id}`}
download={`converted_files_${job.id}.tar`} download={`converted_files_${job.id}.tar`}
@ -35,7 +35,7 @@ function ResultsArticle({
type="button" type="button"
class="float-right w-40 btn-primary" class="float-right w-40 btn-primary"
{...(files.length !== job.num_files ? { disabled: true, "aria-busy": "true" } : "")} {...(files.length !== job.num_files ? { disabled: true, "aria-busy": "true" } : "")}
> >
{files.length === job.num_files ? "Download All" : "Converting..."} {files.length === job.num_files ? "Download All" : "Converting..."}
</button> </button>
</a> </a>

View file

@ -34,7 +34,9 @@ export const root = new Elysia()
let user: ({ id: string } & JWTPayloadSpec) | false = false; let user: ({ id: string } & JWTPayloadSpec) | false = false;
if (ALLOW_UNAUTHENTICATED) { if (ALLOW_UNAUTHENTICATED) {
const newUserId = String( const newUserId = String(
UNAUTHENTICATED_USER_SHARING ? 0 : randomInt(2 ** 24, Math.min(2 ** 48 + 2 ** 24 - 1, Number.MAX_SAFE_INTEGER)), UNAUTHENTICATED_USER_SHARING
? 0
: randomInt(2 ** 24, Math.min(2 ** 48 + 2 ** 24 - 1, Number.MAX_SAFE_INTEGER)),
); );
const accessToken = await jwt.sign({ const accessToken = await jwt.sign({
id: newUserId, id: newUserId,

View file

@ -44,4 +44,4 @@
/* lime-400 */ /* lime-400 */
--accent-400: oklch(84.1% 0.238 128.85); --accent-400: oklch(84.1% 0.238 128.85);
} }
} }