210 lines
6.3 KiB
TypeScript
210 lines
6.3 KiB
TypeScript
import type { NextApiRequest, NextApiResponse } from "next";
|
|
import readFile from "@/lib/api/storage/readFile";
|
|
import { prisma } from "@/lib/api/db";
|
|
import { ArchivedFormat } from "@/types/global";
|
|
import verifyUser from "@/lib/api/verifyUser";
|
|
import getPermission from "@/lib/api/getPermission";
|
|
import { UsersAndCollections } from "@prisma/client";
|
|
import formidable from "formidable";
|
|
import createFile from "@/lib/api/storage/createFile";
|
|
import fs from "fs";
|
|
import verifyToken from "@/lib/api/verifyToken";
|
|
import generatePreview from "@/lib/api/generatePreview";
|
|
import createFolder from "@/lib/api/storage/createFolder";
|
|
|
|
export const config = {
|
|
api: {
|
|
bodyParser: false,
|
|
},
|
|
};
|
|
|
|
export default async function Index(req: NextApiRequest, res: NextApiResponse) {
|
|
const linkId = Number(req.query.linkId);
|
|
const format = Number(req.query.format);
|
|
const isPreview = Boolean(req.query.preview);
|
|
|
|
let suffix: string;
|
|
|
|
if (format === ArchivedFormat.png) suffix = ".png";
|
|
else if (format === ArchivedFormat.jpeg) suffix = ".jpeg";
|
|
else if (format === ArchivedFormat.pdf) suffix = ".pdf";
|
|
else if (format === ArchivedFormat.readability) suffix = "_readability.json";
|
|
else if (format === ArchivedFormat.monolith) suffix = ".html";
|
|
|
|
//@ts-ignore
|
|
if (!linkId || !suffix)
|
|
return res.status(401).json({ response: "Invalid parameters." });
|
|
|
|
if (req.method === "GET") {
|
|
const token = await verifyToken({ req });
|
|
const userId = typeof token === "string" ? undefined : token?.id;
|
|
|
|
const collectionIsAccessible = await prisma.collection.findFirst({
|
|
where: {
|
|
links: {
|
|
some: {
|
|
id: linkId,
|
|
},
|
|
},
|
|
OR: [
|
|
{ ownerId: userId || -1 },
|
|
{ members: { some: { userId: userId || -1 } } },
|
|
{ isPublic: true },
|
|
],
|
|
},
|
|
});
|
|
|
|
if (!collectionIsAccessible)
|
|
return res
|
|
.status(401)
|
|
.json({ response: "You don't have access to this collection." });
|
|
|
|
if (isPreview) {
|
|
const { file, contentType, status } = await readFile(
|
|
`archives/preview/${collectionIsAccessible.id}/${linkId}.jpeg`
|
|
);
|
|
|
|
res.setHeader("Content-Type", contentType).status(status as number);
|
|
|
|
return res.send(file);
|
|
} else {
|
|
const { file, contentType, status } = await readFile(
|
|
`archives/${collectionIsAccessible.id}/${linkId + suffix}`
|
|
);
|
|
|
|
res.setHeader("Content-Type", contentType).status(status as number);
|
|
|
|
return res.send(file);
|
|
}
|
|
} else if (req.method === "POST") {
|
|
if (process.env.NEXT_PUBLIC_DEMO === "true")
|
|
return res.status(400).json({
|
|
response:
|
|
"This action is disabled because this is a read-only demo of Linkwarden.",
|
|
});
|
|
|
|
const user = await verifyUser({ req, res });
|
|
if (!user) return;
|
|
|
|
const collectionPermissions = await getPermission({
|
|
userId: user.id,
|
|
linkId,
|
|
});
|
|
|
|
if (!collectionPermissions)
|
|
return res.status(400).json({
|
|
response: "Collection is not accessible.",
|
|
});
|
|
|
|
const memberHasAccess = collectionPermissions.members.some(
|
|
(e: UsersAndCollections) => e.userId === user.id && e.canCreate
|
|
);
|
|
|
|
if (!(collectionPermissions.ownerId === user.id || memberHasAccess))
|
|
return res.status(400).json({
|
|
response: "Collection is not accessible.",
|
|
});
|
|
|
|
// await uploadHandler(linkId, )
|
|
|
|
const MAX_LINKS_PER_USER = Number(process.env.MAX_LINKS_PER_USER || 30000);
|
|
|
|
const numberOfLinksTheUserHas = await prisma.link.count({
|
|
where: {
|
|
collection: {
|
|
ownerId: user.id,
|
|
},
|
|
},
|
|
});
|
|
|
|
if (numberOfLinksTheUserHas > MAX_LINKS_PER_USER)
|
|
return res.status(400).json({
|
|
response:
|
|
"Each collection owner can only have a maximum of ${MAX_LINKS_PER_USER} Links.",
|
|
});
|
|
|
|
const NEXT_PUBLIC_MAX_FILE_BUFFER = Number(
|
|
process.env.NEXT_PUBLIC_MAX_FILE_BUFFER || 10
|
|
);
|
|
|
|
const form = formidable({
|
|
maxFields: 1,
|
|
maxFiles: 1,
|
|
maxFileSize: NEXT_PUBLIC_MAX_FILE_BUFFER * 1024 * 1024,
|
|
});
|
|
|
|
form.parse(req, async (err, fields, files) => {
|
|
const allowedMIMETypes = [
|
|
"application/pdf",
|
|
"image/png",
|
|
"image/jpg",
|
|
"image/jpeg",
|
|
];
|
|
|
|
if (
|
|
err ||
|
|
!files.file ||
|
|
!files.file[0] ||
|
|
!allowedMIMETypes.includes(files.file[0].mimetype || "")
|
|
) {
|
|
// Handle parsing error
|
|
return res.status(400).json({
|
|
response: `Sorry, we couldn't process your file. Please ensure it's a PDF, PNG, or JPG format and doesn't exceed ${NEXT_PUBLIC_MAX_FILE_BUFFER}MB.`,
|
|
});
|
|
} else {
|
|
const fileBuffer = fs.readFileSync(files.file[0].filepath);
|
|
|
|
if (
|
|
Buffer.byteLength(fileBuffer) >
|
|
1024 * 1024 * Number(NEXT_PUBLIC_MAX_FILE_BUFFER)
|
|
)
|
|
return res.status(400).json({
|
|
response: `Sorry, we couldn't process your file. Please ensure it's a PDF, PNG, or JPG format and doesn't exceed ${NEXT_PUBLIC_MAX_FILE_BUFFER}MB.`,
|
|
});
|
|
|
|
const linkStillExists = await prisma.link.findUnique({
|
|
where: { id: linkId },
|
|
});
|
|
|
|
if (linkStillExists && files.file[0].mimetype?.includes("image")) {
|
|
const collectionId = collectionPermissions.id as number;
|
|
createFolder({
|
|
filePath: `archives/preview/${collectionId}`,
|
|
});
|
|
|
|
generatePreview(fileBuffer, collectionId, linkId);
|
|
}
|
|
|
|
if (linkStillExists) {
|
|
await createFile({
|
|
filePath: `archives/${collectionPermissions.id}/${linkId + suffix}`,
|
|
data: fileBuffer,
|
|
});
|
|
|
|
await prisma.link.update({
|
|
where: { id: linkId },
|
|
data: {
|
|
preview: files.file[0].mimetype?.includes("pdf")
|
|
? "unavailable"
|
|
: undefined,
|
|
image: files.file[0].mimetype?.includes("image")
|
|
? `archives/${collectionPermissions.id}/${linkId + suffix}`
|
|
: null,
|
|
pdf: files.file[0].mimetype?.includes("pdf")
|
|
? `archives/${collectionPermissions.id}/${linkId + suffix}`
|
|
: null,
|
|
lastPreserved: new Date().toISOString(),
|
|
},
|
|
});
|
|
}
|
|
|
|
fs.unlinkSync(files.file[0].filepath);
|
|
}
|
|
|
|
return res.status(200).json({
|
|
response: files,
|
|
});
|
|
});
|
|
}
|
|
}
|