From 96fa7e2f555ac0e8ea15ad9568e25718def5a2e0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 12:53:14 +0000 Subject: [PATCH 1/8] chore(deps): update oven/bun docker tag to v1.1.24 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index fe552ee..cfba78b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM oven/bun:1.1.23-alpine as base +FROM oven/bun:1.1.24-alpine as base WORKDIR /app # install dependencies into temp directory From 2078cb0ee0be877647c7a8b7fc53e200fd6add46 Mon Sep 17 00:00:00 2001 From: A3 <59868436+101br03k@users.noreply.github.com> Date: Sat, 17 Aug 2024 21:31:21 +0200 Subject: [PATCH 2/8] added container name and restart policy --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0f79e98..92e8efd 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ Any missing converter? Open an issue or pull request! services: convertx: image: ghcr.io/c4illin/convertx + container_name: convertx + restart: unless-stopped ports: - "3000:3000" environment: # Defaults are listed below. All are optional. From 83bf78fd57e115cbe1b056d065ae984ab9d6d320 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 06:35:24 +0000 Subject: [PATCH 3/8] chore(deps): update oven/bun docker tag to v1.1.25 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index cfba78b..8018a55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM oven/bun:1.1.24-alpine as base +FROM oven/bun:1.1.25-alpine as base WORKDIR /app # install dependencies into temp directory From 8ca4f1587df7f358893941c656d78d75f04dac93 Mon Sep 17 00:00:00 2001 From: C4illin Date: Fri, 23 Aug 2024 14:30:02 +0200 Subject: [PATCH 4/8] fix: pdf support in vips --- Dockerfile | 4 +++- src/converters/vips.ts | 31 +++++++++++++++++++------------ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8018a55..db620cf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM oven/bun:1.1.25-alpine as base +FROM oven/bun:1.1.25-alpine AS base WORKDIR /app # install dependencies into temp directory @@ -40,6 +40,8 @@ RUN apk --no-cache add \ graphicsmagick \ ghostscript \ vips-tools \ + vips-poppler \ + vips-jxl \ libjxl-tools # this might be needed for some latex use cases, will add it if needed. diff --git a/src/converters/vips.ts b/src/converters/vips.ts index 5522d44..09f3d68 100644 --- a/src/converters/vips.ts +++ b/src/converters/vips.ts @@ -113,22 +113,29 @@ export function convert( // .toFormat(convertTo) // .toFile(targetPath); // } + let action = "copy"; + if (fileType === "pdf") { + action = "pdfload"; + } return new Promise((resolve, reject) => { - exec(`vips copy "${filePath}" "${targetPath}"`, (error, stdout, stderr) => { - if (error) { - reject(`error: ${error}`); - } + exec( + `vips ${action} "${filePath}" "${targetPath}"`, + (error, stdout, stderr) => { + if (error) { + reject(`error: ${error}`); + } - if (stdout) { - console.log(`stdout: ${stdout}`); - } + if (stdout) { + console.log(`stdout: ${stdout}`); + } - if (stderr) { - console.error(`stderr: ${stderr}`); - } + if (stderr) { + console.error(`stderr: ${stderr}`); + } - resolve("success"); - }); + resolve("success"); + }, + ); }); } From f0d0e4392983c3e4c530304ea88e023fda9bcac0 Mon Sep 17 00:00:00 2001 From: C4illin Date: Fri, 23 Aug 2024 15:09:49 +0200 Subject: [PATCH 5/8] feat: add option for unauthenticated file conversions #114 --- README.md | 1 + compose.yaml | 1 + src/index.tsx | 62 ++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 92e8efd..8c83d0c 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ services: - ACCOUNT_REGISTRATION=false # true or false, doesn't matter for the first account (e.g. keep this to false if you only want one account) - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() by default - HTTP_ALLOWED=false # setting this to true is unsafe, only set this to true locally + - ALLOW_UNAUTHENTICATED=false # allows anyone to use the service without logging in, only set this to true locally volumes: - convertx:/app/data ``` diff --git a/compose.yaml b/compose.yaml index 5b870f5..ef4cf7c 100644 --- a/compose.yaml +++ b/compose.yaml @@ -8,5 +8,6 @@ services: environment: - ACCOUNT_REGISTRATION=true - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 + - ALLOW_UNAUTHENTICATED=true ports: - 3000:3000 diff --git a/src/index.tsx b/src/index.tsx index d0ca44c..864adae 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,10 +1,10 @@ import { Database } from "bun:sqlite"; -import { randomUUID } from "node:crypto"; +import { randomUUID, randomInt } from "node:crypto"; import { rmSync } from "node:fs"; import { mkdir, unlink } from "node:fs/promises"; import cookie from "@elysiajs/cookie"; import { html } from "@elysiajs/html"; -import { jwt } from "@elysiajs/jwt"; +import { jwt, type JWTPayloadSpec } from "@elysiajs/jwt"; import { staticPlugin } from "@elysiajs/static"; import { Elysia, t } from "elysia"; import { BaseHtml } from "./components/base"; @@ -30,6 +30,8 @@ const ACCOUNT_REGISTRATION = process.env.ACCOUNT_REGISTRATION === "true" || false; const HTTP_ALLOWED = process.env.HTTP_ALLOWED === "true" || false; +const ALLOW_UNAUTHENTICATED = + process.env.ALLOW_UNAUTHENTICATED === "true" || false; // fileNames: fileNames, // filesToConvert: fileNames.length, @@ -403,25 +405,53 @@ const app = new Elysia({ return redirect("/setup", 302); } - if (!auth?.value) { + if (!auth?.value && !ALLOW_UNAUTHENTICATED) { return redirect("/login", 302); } + // validate jwt - const user = await jwt.verify(auth.value); - if (!user) { - return redirect("/login", 302); + let user: ({ id: string } & JWTPayloadSpec) | false = false; + if (auth?.value) { + user = await jwt.verify(auth.value); + + if (user !== false && user.id) { + // make sure user exists in db + const existingUser = db + .query("SELECT * FROM users WHERE id = ?") + .as(User) + .get(user.id); + + if (!existingUser) { + if (auth?.value) { + auth.remove(); + } + return redirect("/login", 302); + } + } + } else if (ALLOW_UNAUTHENTICATED) { + const newUserId = String(randomInt(2 ^ 24, Number.MAX_SAFE_INTEGER)); + const accessToken = await jwt.sign({ + id: newUserId, + }); + + user = { id: newUserId }; + if (!auth) { + return { + message: "No auth cookie, perhaps your browser is blocking cookies.", + }; + } + + // set cookie + auth.set({ + value: accessToken, + httpOnly: true, + secure: !HTTP_ALLOWED, + maxAge: 60 * 60 * 24 * 1, + sameSite: "strict", + }); } - // make sure user exists in db - const existingUser = db - .query("SELECT * FROM users WHERE id = ?") - .as(User) - .get(user.id); - - if (!existingUser) { - if (auth?.value) { - auth.remove(); - } + if (!user) { return redirect("/login", 302); } From bc4ad492852fad8cb832a0c03485cccdd7f7b117 Mon Sep 17 00:00:00 2001 From: C4illin Date: Fri, 23 Aug 2024 15:18:43 +0200 Subject: [PATCH 6/8] fix: keep unauthenticated user logged in if allowed #114 --- src/index.tsx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 864adae..ad11869 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -415,21 +415,23 @@ const app = new Elysia({ user = await jwt.verify(auth.value); if (user !== false && user.id) { - // make sure user exists in db - const existingUser = db - .query("SELECT * FROM users WHERE id = ?") - .as(User) - .get(user.id); + if (Number.parseInt(user.id) < 2 ** 24 || !ALLOW_UNAUTHENTICATED) { + // make sure user exists in db + const existingUser = db + .query("SELECT * FROM users WHERE id = ?") + .as(User) + .get(user.id); - if (!existingUser) { - if (auth?.value) { - auth.remove(); + if (!existingUser) { + if (auth?.value) { + auth.remove(); + } + return redirect("/login", 302); } - return redirect("/login", 302); } } } else if (ALLOW_UNAUTHENTICATED) { - const newUserId = String(randomInt(2 ^ 24, Number.MAX_SAFE_INTEGER)); + const newUserId = String(randomInt(2 ** 24, Number.MAX_SAFE_INTEGER)); const accessToken = await jwt.sign({ id: newUserId, }); From 74561740227d973e54eb196d3a6dabbd008dba74 Mon Sep 17 00:00:00 2001 From: C4illin Date: Fri, 23 Aug 2024 19:16:25 +0200 Subject: [PATCH 7/8] chore(deps): update dependencies --- bun.lockb | Bin 161868 -> 164052 bytes package.json | 16 ++++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bun.lockb b/bun.lockb index 9ec40730a2e6757aac02c2c064ecfa98af8ddeb0..362a8799c646f28527f8f3ccf44ed64d0993c3d2 100755 GIT binary patch delta 33071 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 delta 31617 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 Im%jS{0DgpQxBvhE diff --git a/package.json b/package.json index f06e408..c472147 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@elysiajs/html": "1.0.2", "@elysiajs/jwt": "^1.1.0", "@elysiajs/static": "1.0.3", - "elysia": "^1.1.5" + "elysia": "^1.1.7" }, "module": "src/index.tsx", "type": "module", @@ -26,18 +26,18 @@ "devDependencies": { "@biomejs/biome": "1.8.3", "@eslint/compat": "^1.1.1", - "@eslint/js": "^9.8.0", + "@eslint/js": "^9.9.0", "@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": "^9.6.0", - "@types/node": "^22.1.0", - "@typescript-eslint/eslint-plugin": "^8.0.1", - "@typescript-eslint/parser": "^8.0.1", + "@types/node": "^22.5.0", + "@typescript-eslint/eslint-plugin": "^8.2.0", + "@typescript-eslint/parser": "^8.2.0", "cpy-cli": "^5.0.0", - "eslint": "^9.8.0", + "eslint": "^9.9.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-deprecation": "^3.0.0", "eslint-plugin-eslint-comments": "^3.2.0", @@ -45,11 +45,11 @@ "eslint-plugin-isaacscript": "^3.12.2", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-simple-import-sort": "^12.1.1", - "knip": "^5.27.0", + "knip": "^5.27.3", "npm-run-all2": "^6.2.2", "prettier": "^3.3.3", "typescript": "^5.5.4", - "typescript-eslint": "^8.0.1" + "typescript-eslint": "^8.2.0" }, "trustedDependencies": ["@biomejs/biome"] } From d5eeef9f6884b2bb878508bed97ea9ceaa662995 Mon Sep 17 00:00:00 2001 From: C4illin Date: Fri, 23 Aug 2024 19:14:06 +0200 Subject: [PATCH 8/8] feat: add resvg converter --- Dockerfile | 8 +++++++ README.md | 3 ++- src/converters/main.ts | 9 ++++++++ src/converters/resvg.ts | 41 ++++++++++++++++++++++++++++++++++++ src/helpers/printVersions.ts | 10 +++++++++ 5 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/converters/resvg.ts diff --git a/Dockerfile b/Dockerfile index db620cf..b920f7b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ FROM oven/bun:1.1.25-alpine AS base +LABEL org.opencontainers.image.source="https://github.com/C4illin/ConvertX" WORKDIR /app # install dependencies into temp directory @@ -13,6 +14,12 @@ RUN mkdir -p /temp/prod COPY package.json bun.lockb /temp/prod/ RUN cd /temp/prod && bun install --frozen-lockfile --production +FROM base AS builder +RUN apk --no-cache add curl gcc +ENV PATH=/root/.cargo/bin:$PATH +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +RUN cargo install resvg + # copy node_modules from temp directory # then copy all (non-ignored) project files into the image # FROM base AS prerelease @@ -48,6 +55,7 @@ RUN apk --no-cache add \ # texmf-dist-fontsextra \ COPY --from=install /temp/prod/node_modules node_modules +COPY --from=builder /root/.cargo/bin/resvg /usr/local/bin/resvg # COPY --from=prerelease /app/src/index.tsx /app/src/ # COPY --from=prerelease /app/package.json . COPY . . diff --git a/README.md b/README.md index 8c83d0c..f95ae4e 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,9 @@ A self-hosted online file converter. Supports 831 different formats. Written wit | Converter | Use case | Converts from | Converts to | |------------------------------------------------------------------------------|---------------|---------------|-------------| | [libjxl](https://github.com/libjxl/libjxl) | JPEG XL | 11 | 11 | +| [resvg](https://github.com/RazrFalcon/resvg) | SVG | 1 | 1 | | [Vips](https://github.com/libvips/libvips) | Images | 45 | 23 | -| [XeLaTeX](https://tug.org/xetex/) | Documents | 1 | 1 | +| [XeLaTeX](https://tug.org/xetex/) | LaTeX | 1 | 1 | | [Pandoc](https://pandoc.org/) | Documents | 43 | 65 | | [GraphicsMagick](http://www.graphicsmagick.org/) | Images | 166 | 133 | | [FFmpeg](https://ffmpeg.org/) | Video | ~473 | ~280 | diff --git a/src/converters/main.ts b/src/converters/main.ts index 076c03e..6b86fea 100644 --- a/src/converters/main.ts +++ b/src/converters/main.ts @@ -25,6 +25,11 @@ import { properties as propertiesLibjxl, } from "./libjxl"; +import { + convert as convertresvg, + properties as propertiesresvg, +} from "./resvg"; + import { normalizeFiletype } from "../helpers/normalizeFiletype"; // This should probably be reconstructed so that the functions are not imported instead the functions hook into this to make the converters more modular @@ -59,6 +64,10 @@ const properties: { properties: propertiesLibjxl, converter: convertLibjxl, }, + resvg: { + properties: propertiesresvg, + converter: convertresvg, + }, vips: { properties: propertiesImage, converter: convertImage, diff --git a/src/converters/resvg.ts b/src/converters/resvg.ts new file mode 100644 index 0000000..e971378 --- /dev/null +++ b/src/converters/resvg.ts @@ -0,0 +1,41 @@ +import { exec } from "node:child_process"; + +export const properties = { + from: { + images: ["svg"], + }, + to: { + images: ["png"], + }, +}; + + +export function convert( + filePath: string, + fileType: string, + convertTo: string, + targetPath: string, + // biome-ignore lint/suspicious/noExplicitAny: + options?: any, +): Promise { + return new Promise((resolve, reject) => { + exec( + `resvg "${filePath}" "${targetPath}"`, + (error, stdout, stderr) => { + if (error) { + reject(`error: ${error}`); + } + + if (stdout) { + console.log(`stdout: ${stdout}`); + } + + if (stderr) { + console.error(`stderr: ${stderr}`); + } + + resolve("success"); + }, + ); + }); +} \ No newline at end of file diff --git a/src/helpers/printVersions.ts b/src/helpers/printVersions.ts index a36da86..51f7433 100644 --- a/src/helpers/printVersions.ts +++ b/src/helpers/printVersions.ts @@ -73,6 +73,16 @@ if (process.env.NODE_ENV === "production") { } }); + exec("resvg -V", (error, stdout) => { + if (error) { + console.error("resvg is not installed"); + } + + if (stdout) { + console.log(`resvg v${stdout.split("\n")[0]}`); + } + }); + exec("bun -v", (error, stdout) => { if (error) { console.error("Bun is not installed. wait what");