-
+
*/}
-
{
- if (router.pathname.startsWith("/public")) {
- router.push(
- `/public/collections/${
+
- Back{" "}
-
- to{" "}
-
- {router.pathname.startsWith("/public")
- ? linkCollection?.name || link?.collection?.name
- : "Dashboard"}
-
+
+ {router.pathname.startsWith("/public")
+ ? linkCollection?.name || link?.collection?.name
+ : "Dashboard"}
-
+
-
+
{link?.collection?.ownerId === userId ||
linkCollection?.members.some(
(e) => e.userId === userId && e.canUpdate
) ? (
{
- link
- ? setModal({
- modal: "LINK",
- state: true,
- active: link,
- method: "UPDATE",
- })
- : undefined;
- }}
- className={`hover:opacity-60 duration-100 py-2 px-2 cursor-pointer flex items-center gap-4 w-full rounded-md h-8`}
+ onClick={() => setEditLinkModal(true)}
+ className={`btn btn-ghost btn-square btn-sm`}
>
) : undefined}
{
- link
- ? setModal({
- modal: "LINK",
- state: true,
- active: link,
- method: "FORMATS",
- })
- : undefined;
- }}
+ onClick={() => setPreservedFormatsModal(true)}
title="Preserved Formats"
- className={`hover:opacity-60 duration-100 py-2 px-2 cursor-pointer flex items-center gap-4 w-full rounded-md h-8`}
+ className={`btn btn-ghost btn-square btn-sm`}
>
@@ -168,18 +161,16 @@ export default function LinkLayout({ children }: Props) {
(e) => e.userId === userId && e.canDelete
) ? (
{
- if (link?.id) {
- removeLink(link.id);
- router.back();
- }
+ onClick={(e) => {
+ (document?.activeElement as HTMLElement)?.blur();
+ e.shiftKey ? deleteLink() : setDeleteLinkModal(true);
}}
title="Delete"
- className={`hover:opacity-60 duration-100 py-2 px-2 cursor-pointer flex items-center gap-4 w-full rounded-md h-8`}
+ className={`btn btn-ghost btn-square btn-sm`}
>
) : undefined}
@@ -201,6 +192,24 @@ export default function LinkLayout({ children }: Props) {
) : null}
+ {link && editLinkModal ? (
+
setEditLinkModal(false)}
+ activeLink={link}
+ />
+ ) : undefined}
+ {link && deleteLinkModal ? (
+ setDeleteLinkModal(false)}
+ activeLink={link}
+ />
+ ) : undefined}
+ {link && preservedFormatsModal ? (
+ setPreservedFormatsModal(false)}
+ activeLink={link}
+ />
+ ) : undefined}
>
);
diff --git a/lib/api/controllers/links/postLink.ts b/lib/api/controllers/links/postLink.ts
index c6755b4c..3d28a3f1 100644
--- a/lib/api/controllers/links/postLink.ts
+++ b/lib/api/controllers/links/postLink.ts
@@ -1,6 +1,6 @@
import { prisma } from "@/lib/api/db";
import { LinkIncludingShortenedCollectionAndTags } from "@/types/global";
-import getTitle from "@/lib/api/getTitle";
+import getTitle from "@/lib/shared/getTitle";
import urlHandler from "@/lib/api/urlHandler";
import { UsersAndCollections } from "@prisma/client";
import getPermission from "@/lib/api/getPermission";
@@ -75,7 +75,6 @@ export default async function postLink(
name: link.name,
description,
type: linkType,
- readabilityPath: "pending",
collection: {
connectOrCreate: {
where: {
@@ -118,11 +117,33 @@ export default async function postLink(
? urlHandler(newLink.id, newLink.url, userId)
: undefined;
- linkType === "pdf" ? pdfHandler(newLink.id, newLink.url) : undefined;
+ newLink.url && linkType === "pdf"
+ ? pdfHandler(newLink.id, newLink.url)
+ : undefined;
- linkType === "image"
+ newLink.url && linkType === "image"
? imageHandler(newLink.id, newLink.url, imageExtension)
: undefined;
+ !newLink.url && linkType === "pdf"
+ ? await prisma.link.update({
+ where: { id: newLink.id },
+ data: {
+ pdfPath: "pending",
+ lastPreserved: new Date().toISOString(),
+ },
+ })
+ : undefined;
+
+ !newLink.url && linkType === "image"
+ ? await prisma.link.update({
+ where: { id: newLink.id },
+ data: {
+ screenshotPath: "pending",
+ lastPreserved: new Date().toISOString(),
+ },
+ })
+ : undefined;
+
return { response: newLink, status: 200 };
}
diff --git a/lib/api/urlHandler.ts b/lib/api/urlHandler.ts
index 8795d76b..e61d0ac5 100644
--- a/lib/api/urlHandler.ts
+++ b/lib/api/urlHandler.ts
@@ -37,6 +37,23 @@ export default async function urlHandler(
const content = await page.content();
+ // TODO
+ // const session = await page.context().newCDPSession(page);
+
+ // const doc = await session.send("Page.captureSnapshot", {
+ // format: "mhtml",
+ // });
+
+ // const saveDocLocally = (doc: any) => {
+ // console.log(doc);
+ // return createFile({
+ // data: doc,
+ // filePath: `archives/${targetLink.collectionId}/${linkId}.mhtml`,
+ // });
+ // };
+
+ // saveDocLocally(doc.data);
+
// Readability
const window = new JSDOM("").window;
diff --git a/lib/api/getTitle.ts b/lib/shared/getTitle.ts
similarity index 100%
rename from lib/api/getTitle.ts
rename to lib/shared/getTitle.ts
diff --git a/package.json b/package.json
index 479fa553..d5de5be5 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"@prisma/client": "^4.16.2",
"@stripe/stripe-js": "^1.54.1",
"@types/crypto-js": "^4.1.1",
+ "@types/formidable": "^3.4.5",
"@types/node": "20.4.4",
"@types/nodemailer": "^6.4.8",
"@types/react": "18.2.14",
@@ -37,6 +38,7 @@
"dompurify": "^3.0.6",
"eslint": "8.46.0",
"eslint-config-next": "13.4.9",
+ "formidable": "^3.5.1",
"framer-motion": "^10.16.4",
"jsdom": "^22.1.0",
"lottie-web": "^5.12.2",
diff --git a/pages/_app.tsx b/pages/_app.tsx
index 3f52d9b6..eea08e48 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -47,7 +47,7 @@ export default function App({
reverseOrder={false}
toastOptions={{
className:
- "border border-sky-100 dark:border-neutral-700 dark:bg-neutral-900 dark:text-white",
+ "border border-sky-100 dark:border-neutral-700 dark:bg-neutral-800 dark:text-white",
}}
/>
diff --git a/pages/api/v1/archives/[linkId].ts b/pages/api/v1/archives/[linkId].ts
index faade60a..8686c154 100644
--- a/pages/api/v1/archives/[linkId].ts
+++ b/pages/api/v1/archives/[linkId].ts
@@ -3,49 +3,142 @@ import readFile from "@/lib/api/storage/readFile";
import { getToken } from "next-auth/jwt";
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";
+
+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);
- let suffix;
+ 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";
+ //@ts-ignore
if (!linkId || !suffix)
return res.status(401).json({ response: "Invalid parameters." });
- const token = await getToken({ req });
- const userId = token?.id;
+ if (req.method === "GET") {
+ const token = await getToken({ req });
+ const userId = token?.id;
- const collectionIsAccessible = await prisma.collection.findFirst({
- where: {
- links: {
- some: {
- id: linkId,
+ const collectionIsAccessible = await prisma.collection.findFirst({
+ where: {
+ links: {
+ some: {
+ id: linkId,
+ },
},
+ OR: [
+ { ownerId: userId || -1 },
+ { members: { some: { userId: userId || -1 } } },
+ { isPublic: true },
+ ],
},
- 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 (!collectionIsAccessible)
+ return res
+ .status(401)
+ .json({ response: "You don't have access to this collection." });
- const { file, contentType, status } = await readFile(
- `archives/${collectionIsAccessible.id}/${linkId + suffix}`
- );
+ const { file, contentType, status } = await readFile(
+ `archives/${collectionIsAccessible.id}/${linkId + suffix}`
+ );
- res.setHeader("Content-Type", contentType).status(status as number);
+ res.setHeader("Content-Type", contentType).status(status as number);
- return res.send(file);
+ return res.send(file);
+ }
+ // else if (req.method === "POST") {
+ // const user = await verifyUser({ req, res });
+ // if (!user) return;
+
+ // const collectionPermissions = await getPermission({
+ // userId: user.id,
+ // linkId,
+ // });
+
+ // const memberHasAccess = collectionPermissions?.members.some(
+ // (e: UsersAndCollections) => e.userId === user.id && e.canCreate
+ // );
+
+ // if (!(collectionPermissions?.ownerId === user.id || memberHasAccess))
+ // return { response: "Collection is not accessible.", status: 401 };
+
+ // // await uploadHandler(linkId, )
+
+ // const MAX_UPLOAD_SIZE = Number(process.env.NEXT_PUBLIC_MAX_UPLOAD_SIZE);
+
+ // const form = formidable({
+ // maxFields: 1,
+ // maxFiles: 1,
+ // maxFileSize: MAX_UPLOAD_SIZE || 30 * 1048576,
+ // });
+
+ // 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(500).json({
+ // response: `Sorry, we couldn't process your file. Please ensure it's a PDF, PNG, or JPG format and doesn't exceed ${MAX_UPLOAD_SIZE}MB.`,
+ // });
+ // } else {
+ // const fileBuffer = fs.readFileSync(files.file[0].filepath);
+
+ // const linkStillExists = await prisma.link.findUnique({
+ // where: { id: linkId },
+ // });
+
+ // if (linkStillExists) {
+ // await createFile({
+ // filePath: `archives/${collectionPermissions?.id}/${
+ // linkId + suffix
+ // }`,
+ // data: fileBuffer,
+ // });
+
+ // await prisma.link.update({
+ // where: { id: linkId },
+ // data: {
+ // screenshotPath: `archives/${collectionPermissions?.id}/${
+ // linkId + suffix
+ // }`,
+ // lastPreserved: new Date().toISOString(),
+ // },
+ // });
+ // }
+
+ // fs.unlinkSync(files.file[0].filepath);
+ // }
+
+ // return res.status(200).json({
+ // response: files,
+ // });
+ // });
+ // }
}
diff --git a/pages/collections/[id].tsx b/pages/collections/[id].tsx
index 9bd57144..08ff8dc5 100644
--- a/pages/collections/[id].tsx
+++ b/pages/collections/[id].tsx
@@ -111,7 +111,10 @@ export default function Index() {
onClick={() => setEditCollectionSharingModal(true)}
>
{collectionOwner.id ? (
-
+
) : undefined}
{activeCollection.members
.sort((a, b) => (a.userId as number) - (b.userId as number))
@@ -121,19 +124,20 @@ export default function Index() {
key={i}
src={e.user.image ? e.user.image : undefined}
className="-ml-3"
+ name={e.user.name}
/>
);
})
.slice(0, 3)}
{activeCollection.members.length - 3 > 0 ? (
-
+
+{activeCollection.members.length - 3}
) : null}
-
+
By {collectionOwner.name}
{activeCollection.members.length > 0
? ` and ${activeCollection.members.length} others`
diff --git a/pages/collections/index.tsx b/pages/collections/index.tsx
index ca3fd4a1..477d92d8 100644
--- a/pages/collections/index.tsx
+++ b/pages/collections/index.tsx
@@ -44,35 +44,6 @@ export default function Collections() {
Collections you own