KI-AGENT: Interne MinIO-Links durch Backend-Downloads ersetzen

This commit is contained in:
2026-08-09 19:20:19 +02:00
parent ee39ee59cc
commit 86e5d49795
2 changed files with 67 additions and 30 deletions

View File

@@ -177,11 +177,49 @@ export default async function exportRoutes(server: FastifyInstance) {
.from(generatedexports)
.where(eq(generatedexports.tenantId, req.user.tenant_id))
console.log(data)
reply.send(data)
reply.send(data.map(item => ({
...item,
url: `/api/exports/${item.id}/download`,
})))
})
server.get("/exports/:id/download", async (req, reply) => {
const { id } = req.params as { id: string }
const exportId = Number(id)
if (!Number.isSafeInteger(exportId)) {
return reply.code(404).send({ error: "Export not found" })
}
const [item] = await server.db
.select()
.from(generatedexports)
.where(eq(generatedexports.id, exportId))
if (
!item ||
item.tenantId !== req.user.tenant_id ||
item.validUntil.getTime() <= Date.now()
) {
return reply.code(404).send({ error: "Export not found" })
}
try {
const { Body, ContentType } = await s3.send(new GetObjectCommand({
Bucket: secrets.S3_BUCKET,
Key: item.filePath,
}))
const filename = item.filePath.split("/").pop() || `export-${item.id}`
reply.header("Content-Type", ContentType || "application/octet-stream")
reply.header("Content-Disposition", `attachment; filename="${filename}"`)
return reply.send(Body as any)
} catch (error) {
console.error(error)
return reply.code(500).send({ error: "Could not download export" })
}
})

View File

@@ -4,7 +4,6 @@ import { s3 } from "../utils/s3"
import {
GetObjectCommand
} from "@aws-sdk/client-s3"
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
import archiver from "archiver"
import { secrets } from "../utils/secrets"
import { saveFile } from "../utils/files"
@@ -241,6 +240,28 @@ export default async function fileRoutes(server: FastifyInstance) {
}
})
// Dateien über das Backend ausliefern, damit interne S3-Endpunkte wie
// http://minio:9000 niemals als URL an den Browser gelangen.
server.get("/files/content/:id", async (req, reply) => {
try {
const { id } = req.params as { id: string }
const file = await loadSingleFileForRequest(req, id)
if (!file) return reply.code(404).send({ error: "File not found" })
const { Body, ContentType } = await s3.send(new GetObjectCommand({
Bucket: secrets.S3_BUCKET,
Key: file.path!
}))
reply.header("Content-Type", ContentType || "application/octet-stream")
reply.header("Content-Disposition", "inline")
return reply.send(Body as any)
} catch (err) {
console.error(err)
return reply.code(500).send({ error: "Could not load file" })
}
})
// -------------------------------------------------------------
@@ -261,13 +282,7 @@ export default async function fileRoutes(server: FastifyInstance) {
const file = await loadSingleFileForRequest(req, id)
if (!file) return reply.code(404).send({ error: "Not found" })
const url = await getSignedUrl(
s3,
new GetObjectCommand({ Bucket: secrets.S3_BUCKET, Key: file.path! }),
{ expiresIn: 900 }
)
return { ...file, url }
return { ...file, url: `/api/files/content/${file.id}` }
} else {
// -------------------------------------------------
// MULTIPLE PRESIGNED URLs
@@ -283,26 +298,10 @@ export default async function fileRoutes(server: FastifyInstance) {
const selected = rows.filter(f => ids.includes(f.id) && f.path)
console.log(selected)
const url = await getSignedUrl(
s3,
new GetObjectCommand({ Bucket: secrets.S3_BUCKET, Key: selected[0].path! }),
{ expiresIn: 900 }
)
console.log(url)
console.log(selected.filter(f => !f.path))
const output = await Promise.all(
selected.map(async (file) => {
const url = await getSignedUrl(
s3,
new GetObjectCommand({ Bucket: secrets.S3_BUCKET, Key: file.path! }),
{ expiresIn: 900 }
)
return { ...file, url }
})
)
const output = selected.map(file => ({
...file,
url: `/api/files/content/${file.id}`
}))
return { files: output }
}