265 lines
8.7 KiB
TypeScript
265 lines
8.7 KiB
TypeScript
import { FastifyInstance } from "fastify"
|
|
import multipart from "@fastify/multipart"
|
|
import { s3 } from "../utils/s3"
|
|
import {GetObjectCommand, PutObjectCommand} from "@aws-sdk/client-s3"
|
|
import {getSignedUrl} from "@aws-sdk/s3-request-presigner";
|
|
import archiver from "archiver"
|
|
import {secrets} from "../utils/secrets"
|
|
|
|
export default async function fileRoutes(server: FastifyInstance) {
|
|
await server.register(multipart,{
|
|
limits: {
|
|
fileSize: 20 * 1024 * 1024, // 20 MB
|
|
}
|
|
})
|
|
|
|
server.post("/files/upload", async (req, reply) => {
|
|
const tenantId = req.user?.tenant_id
|
|
if (!tenantId) return reply.code(401).send({ error: "Unauthorized" })
|
|
|
|
const data:any = await req.file()
|
|
const fileBuffer = await data.toBuffer()
|
|
|
|
console.log(data)
|
|
|
|
|
|
let meta = JSON.parse(data.fields?.meta?.value)
|
|
|
|
if (!data.file) return reply.code(400).send({ error: "No file uploaded" })
|
|
|
|
|
|
const {data:createdFileData,error:createdFileError} = await server.supabase
|
|
.from("files")
|
|
.insert({
|
|
tenant: tenantId,
|
|
})
|
|
.select()
|
|
.single()
|
|
|
|
if(createdFileError) {
|
|
console.log(createdFileError)
|
|
return reply.code(500).send({ error: "Internal Server Error" })
|
|
} else if(createdFileData && data.file) {
|
|
const fileKey = `${tenantId}/filesbyid/${createdFileData.id}/${data.filename}`
|
|
|
|
await s3.send(new PutObjectCommand({
|
|
Bucket: secrets.S3_BUCKET,
|
|
Key: fileKey,
|
|
Body: fileBuffer,
|
|
ContentType: data.mimetype,
|
|
}))
|
|
|
|
//Update File with Corresponding Path
|
|
const {data:updateFileData, error:updateFileError} = await server.supabase
|
|
.from("files")
|
|
.update({
|
|
...meta,
|
|
path: fileKey,
|
|
})
|
|
.eq("id", createdFileData.id)
|
|
|
|
if(updateFileError) {
|
|
console.log(updateFileError)
|
|
return reply.code(500).send({ error: "Internal Server Error" })
|
|
|
|
} else {
|
|
/*const {data:tagData, error:tagError} = await server.supabase
|
|
.from("filetagmembers")
|
|
.insert(tags.map(tag => {
|
|
return {
|
|
file_id: createdFileData.id,
|
|
tag_id: tag
|
|
}
|
|
}))*/
|
|
|
|
return { id: createdFileData.id, filename: data.filename, path: fileKey }
|
|
|
|
}
|
|
|
|
}
|
|
})
|
|
|
|
server.get("/files/:id?", async (req, reply) => {
|
|
const { id } = req.params as { id?: string }
|
|
|
|
if(id) {
|
|
try {
|
|
const {data,error} = await server.supabase.from("files").select("*").eq("id", id).single()
|
|
|
|
return {...data}
|
|
} catch (err) {
|
|
req.log.error(err);
|
|
reply.code(500).send({ error: "Could not generate presigned URL" });
|
|
}
|
|
} else {
|
|
try {
|
|
const {data:supabaseFileEntries,error} = await server.supabase.from("files").select("*, createddocument(*, customer(*))").eq("tenant",req.user.tenant_id)
|
|
|
|
|
|
|
|
return { files: supabaseFileEntries }
|
|
} catch (err) {
|
|
req.log.error(err)
|
|
reply.code(500).send({ error: "Could not generate presigned URLs" })
|
|
}
|
|
}
|
|
|
|
})
|
|
|
|
server.post("/files/download/:id?", async (req, reply) => {
|
|
const { id } = req.params as { id?: string }
|
|
|
|
// @ts-ignore
|
|
const ids = req.body?.ids || []
|
|
|
|
|
|
try {
|
|
if (id) {
|
|
// 🔹 Einzeldownload
|
|
const { data, error } = await server.supabase
|
|
.from("files")
|
|
.select("*")
|
|
.eq("id", id)
|
|
.single()
|
|
|
|
if (error || !data) {
|
|
return reply.code(404).send({ error: "File not found" })
|
|
}
|
|
|
|
const command = new GetObjectCommand({
|
|
Bucket: secrets.S3_BUCKET,
|
|
Key: data.path,
|
|
})
|
|
|
|
const { Body, ContentType } = await s3.send(command)
|
|
|
|
const chunks: any[] = []
|
|
// @ts-ignore
|
|
for await (const chunk of Body) {
|
|
chunks.push(chunk)
|
|
}
|
|
const buffer = Buffer.concat(chunks)
|
|
|
|
reply.header("Content-Type", ContentType || "application/octet-stream")
|
|
reply.header(
|
|
"Content-Disposition",
|
|
`attachment; filename="${data.path.split("/").pop()}"`
|
|
)
|
|
return reply.send(buffer)
|
|
}
|
|
|
|
console.log(ids)
|
|
|
|
if (Array.isArray(ids) && ids.length > 0) {
|
|
// 🔹 Multi-Download → ZIP zurückgeben
|
|
const { data: supabaseFiles, error } = await server.supabase
|
|
.from("files")
|
|
.select("*")
|
|
.in("id", ids)
|
|
|
|
if (error || !supabaseFiles?.length) {
|
|
return reply.code(404).send({ error: "Files not found" })
|
|
}
|
|
|
|
console.log(supabaseFiles)
|
|
|
|
reply.header("Content-Type", "application/zip")
|
|
reply.header("Content-Disposition", "attachment; filename=dateien.zip")
|
|
|
|
const archive = archiver("zip", { zlib: { level: 9 } })
|
|
archive.on("warning", console.warn)
|
|
|
|
for (const entry of supabaseFiles) {
|
|
const command = new GetObjectCommand({
|
|
Bucket: secrets.S3_BUCKET,
|
|
Key: entry.path,
|
|
})
|
|
|
|
const { Body } = await s3.send(command)
|
|
const filename = entry.path.split("/").pop() || entry.id
|
|
console.log(filename)
|
|
archive.append(Body as any, { name: filename })
|
|
}
|
|
|
|
await archive.finalize()
|
|
return reply.send(archive)
|
|
}
|
|
|
|
return reply.code(400).send({ error: "No id or ids provided" })
|
|
} catch (err) {
|
|
console.log(err)
|
|
reply.code(500).send({ error: "Download failed" })
|
|
}
|
|
})
|
|
|
|
server.post("/files/presigned/:id?", async (req, reply) => {
|
|
const { id } = req.params as { id: string };
|
|
const { ids } = req.body as { ids: string[] }
|
|
|
|
if(id) {
|
|
try {
|
|
const {data,error} = await server.supabase.from("files").select("*").eq("id", id).single()
|
|
|
|
const command = new GetObjectCommand({
|
|
Bucket: secrets.S3_BUCKET,
|
|
Key: data.path,
|
|
});
|
|
|
|
// URL für 15 Minuten gültig
|
|
const url = await getSignedUrl(s3, command, { expiresIn: 900 });
|
|
|
|
return { ...data, url };
|
|
} catch (err) {
|
|
req.log.error(err);
|
|
reply.code(500).send({ error: "Could not generate presigned URL" });
|
|
}
|
|
} else {
|
|
if (!Array.isArray(ids) || ids.length === 0) {
|
|
return reply.code(400).send({ error: "No file keys provided" })
|
|
}
|
|
|
|
try {
|
|
const {data:supabaseFileEntries,error} = await server.supabase.from("files").select("*, createddocument(*, customer(*))").eq("tenant",req.user.tenant_id).is("archived",false)
|
|
|
|
console.log(error)
|
|
|
|
let filteredFiles = supabaseFileEntries.filter(i => ids.includes(i.id))
|
|
filteredFiles = filteredFiles.filter(i => i.path)
|
|
|
|
console.log(filteredFiles.filter(i => !i.path))
|
|
|
|
|
|
|
|
let urls = await Promise.all(
|
|
ids.map(async (id) => {
|
|
let file = filteredFiles.find(i => i.id === id)
|
|
|
|
if(!file) return
|
|
|
|
let key = file.path
|
|
if(!key) console.log(file)
|
|
|
|
const command = new GetObjectCommand({
|
|
Bucket: secrets.S3_BUCKET,
|
|
Key: key,
|
|
})
|
|
|
|
const url = await getSignedUrl(s3, command, { expiresIn: 900 }) // 15 min gültig
|
|
|
|
|
|
return {...filteredFiles.find(i => i.id === id), url}
|
|
})
|
|
)
|
|
|
|
urls = urls.filter(i => i)
|
|
|
|
return { files: urls }
|
|
} catch (err) {
|
|
console.log(err)
|
|
reply.code(500).send({ error: "Could not generate presigned URLs" })
|
|
}
|
|
}
|
|
|
|
|
|
})
|
|
} |