feat: new separate frontend and backend folders with fastify as nodejs framework, libreconvert is configured in fastify as route

This commit is contained in:
theoleuthardt 2025-02-14 14:40:35 +01:00
parent 7b584d2fff
commit 9733de9a39
26 changed files with 1171 additions and 61 deletions

9
.gitignore vendored
View file

@ -2,6 +2,7 @@
# dependencies # dependencies
/node_modules /node_modules
/frontend/node_modules
/.pnp /.pnp
.pnp.* .pnp.*
.yarn/* .yarn/*
@ -14,11 +15,11 @@
/coverage /coverage
# next.js # next.js
/.next/ frontend/.next/
/out/ frontend/out/
# production # production
/build frontend/build
# misc # misc
.DS_Store .DS_Store
@ -38,4 +39,4 @@ yarn-error.log*
# typescript # typescript
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts frontend/next-env.d.ts

View file

@ -9,7 +9,7 @@ RUN apk add --no-cache libc6-compat libreoffice ttf-liberation
WORKDIR /app WORKDIR /app
# Install dependencies based on the preferred package manager # Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./ COPY frontend/package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
RUN \ RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \ elif [ -f package-lock.json ]; then npm ci; \

1046
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

28
backend/package.json Normal file
View file

@ -0,0 +1,28 @@
{
"name": "backend",
"version": "1.0.0",
"main": "server.ts",
"scripts": {
"dev": "ts-node server.ts",
"build": "tsc",
"start": "node dist/server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@fastify/cors": "^10.0.2",
"@fastify/multipart": "^9.0.3",
"dotenv": "^16.4.7",
"fastify": "^5.2.1",
"fastify-cors": "^6.0.3",
"fastify-multipart": "^5.3.1",
"libreoffice-convert": "^1.6.0"
},
"devDependencies": {
"@types/node": "^22.13.4",
"ts-node": "^10.9.2",
"typescript": "^5.7.3"
}
}

15
backend/server.ts Normal file
View file

@ -0,0 +1,15 @@
import Fastify from "fastify";
import cors from "@fastify/cors";
import multipart from '@fastify/multipart';
import { libreConvert } from "./src/routes/libreconvert.route";
const app = Fastify({ logger: true });
app.register(cors, { origin: "*" });
app.register(multipart);
app.register(libreConvert);
const PORT = process.env.PORT || 4000;
app.listen({ port: Number(PORT), host: "0.0.0.0" }, () => {
console.log(`🚀Fastify is live on http://localhost:${PORT}`);
});

View file

@ -0,0 +1,30 @@
import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import * as libre from "libreoffice-convert";
import { promisify } from "util";
const libreConvertAsync = promisify(libre.convert);
export async function libreConvert(app: FastifyInstance) {
app.post("/api/libre-convert", async (request: FastifyRequest, reply: FastifyReply) => {
try {
const data = await request.file();
if (!data) {
return reply.status(400).send({ error: "No file uploaded!" });
}
const ext = ".pdf";
const format = ext.substring(1);
const fileBuffer = await data.toBuffer();
const pdfBuffer = await libreConvertAsync(fileBuffer, ext, undefined);
reply
.header("Content-Type", "application/" + format)
.header("Content-Disposition", "attachment; filename=converted" + ext)
.send(pdfBuffer);
} catch (error) {
console.error("Convert error:", error);
reply.status(500).send({ error: "Error while converting!" });
}
});
}

10
backend/tsconfig.json Normal file
View file

@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "ES6",
"module": "CommonJS",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"allowSyntheticDefaultImports": true
}
}

View file

@ -2,6 +2,9 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
output: "standalone", output: "standalone",
env: {
backend_url: "http://localhost:4000",
},
}; };
export default nextConfig; export default nextConfig;

View file

@ -1,9 +1,9 @@
"use client"; "use client";
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import Navbar from "../../components/Navbar"; import Navbar from "../../components/Navbar";
import Footer from "../../components/Footer"; import Footer from "../../components/Footer";
import Button from "@/components/Button"; import Button from "../../components/Button";
import Link from "next/link"; import Link from "next/link";
export default function Home() { export default function Home() {
@ -30,10 +30,13 @@ export default function Home() {
setLoading(true); setLoading(true);
try { try {
const response = await fetch("/api/libre-convert", { const response = await fetch(
method: "POST", process.env.backend_url + "/api/libre-convert",
body: formData, {
}); method: "POST",
body: formData,
},
);
if (!response.ok) { if (!response.ok) {
return new Error(`Error: ${response.statusText}`); return new Error(`Error: ${response.statusText}`);
@ -50,17 +53,33 @@ export default function Home() {
} }
}; };
useEffect(() => {
if (file) {
console.log(file);
}
}, [file]);
return ( return (
<div className="h-screen w-screen bg-black text-white font-noto flex flex-col items-center"> <div className="h-screen w-screen bg-black text-white font-noto flex flex-col items-center">
<Navbar renderHomeLink={true} /> <Navbar renderHomeLink={true} />
<div className="w-screen h-screen flex flex-col items-center justify-center"> <div className="w-screen h-screen flex flex-col items-center justify-center">
<h2 className="text-5xl font-bold text-white mb-16">doc-to-pdf</h2> <h2 className="text-5xl font-bold text-white mb-16">doc-to-pdf</h2>
<table className="border-2 border-white text-xl rounded-xl mb-16">
<thead>
<tr>
<th>Input Format</th>
<th>Output Format</th>
</tr>
</thead>
<tbody>
<tr>
<td>docx</td>
<td>pdf</td>
</tr>
<tr>
<td>doc</td>
<td>pdf</td>
</tr>
<tr>
<td>odt</td>
<td>pdf</td>
</tr>
</tbody>
</table>
<input <input
type="file" type="file"
className="border-2 border-white p-3 rounded-xl text-center text-white" className="border-2 border-white p-3 rounded-xl text-center text-white"

View file

Before

Width:  |  Height:  |  Size: 481 B

After

Width:  |  Height:  |  Size: 481 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before After
Before After

View file

@ -22,6 +22,7 @@
"@/*": ["./src/*"] "@/*": ["./src/*"]
} }
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": [
"next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }

View file

@ -1,43 +0,0 @@
"use strict";
import libre from "libreoffice-convert";
import { promisify } from "util";
import { NextRequest, NextResponse } from "next/server";
const libreConvertAsync = promisify(libre.convert);
export async function POST(request: NextRequest) {
if (request.method !== "POST") {
return NextResponse.json({ error: "Method not allowed" }, { status: 405 });
}
try {
const formData = await request.formData();
const file = formData.get("file") as Blob;
if (!file) {
return NextResponse.json({ error: "No file uploaded!" }, { status: 400 });
}
const arrayBuffer = await file.arrayBuffer();
const fileBuffer = Buffer.from(arrayBuffer);
const pdfBuffer = await libreConvertAsync(
fileBuffer,
".pdf",
"writer_pdf_Export",
);
return new Response(pdfBuffer, {
status: 200,
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": "attachment; filename=converted.pdf",
},
});
} catch (error: unknown) {
console.error("Convert error: ", error);
return NextResponse.json(
{ error: "Error while converting!" },
{ status: 500 },
);
}
}