From 0c6911aaf06577bbce7308dc38a4be65b064f8b3 Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Sun, 19 Nov 2023 16:22:27 -0500 Subject: [PATCH] updated route + bug fixed --- components/Modal/Link/PreservedFormats.tsx | 20 +++++--- .../collectionId/deleteCollectionById.ts | 8 +-- .../collectionId/updateCollectionById.ts | 8 +-- .../links/linkId/deleteLinkById.ts | 6 +-- .../controllers/links/linkId/getLinkById.ts | 6 +-- .../links/linkId/updateLinkById.ts | 6 +-- lib/api/controllers/links/postLink.ts | 10 ++-- lib/api/getPermission.ts | 32 +++--------- pages/api/v1/archives/[...params].ts | 32 ------------ pages/api/v1/archives/[linkId].ts | 49 +++++++++++++++++++ pages/links/[id].tsx | 9 +++- pages/public/links/[id].tsx | 9 +++- types/global.ts | 6 +++ 13 files changed, 98 insertions(+), 103 deletions(-) delete mode 100644 pages/api/v1/archives/[...params].ts create mode 100644 pages/api/v1/archives/[linkId].ts diff --git a/components/Modal/Link/PreservedFormats.tsx b/components/Modal/Link/PreservedFormats.tsx index a21405d..410d3a1 100644 --- a/components/Modal/Link/PreservedFormats.tsx +++ b/components/Modal/Link/PreservedFormats.tsx @@ -1,4 +1,7 @@ -import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; +import { + ArchivedFormat, + LinkIncludingShortenedCollectionAndTags, +} from "@/types/global"; import { useEffect, useState } from "react"; import Link from "next/link"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -65,15 +68,16 @@ export default function PreservedFormats() { } else toast.error(data.response); }; - const handleDownload = (format: "png" | "pdf") => { - const path = `/api/v1/archives/${link?.collection.id}/${link?.id}.${format}`; + const handleDownload = (format: ArchivedFormat) => { + const path = `/api/v1/archives/${link?.id}?format=${format}`; fetch(path) .then((response) => { if (response.ok) { // Create a temporary link and click it to trigger the download const link = document.createElement("a"); link.href = path; - link.download = format === "pdf" ? "PDF" : "Screenshot"; + link.download = + format === ArchivedFormat.screenshot ? "Screenshot" : "PDF"; link.click(); } else { console.error("Failed to download file"); @@ -98,7 +102,7 @@ export default function PreservedFormats() {
handleDownload("png")} + onClick={() => handleDownload(ArchivedFormat.screenshot)} className="cursor-pointer hover:opacity-60 duration-100 p-2 rounded-md" > @@ -133,7 +137,7 @@ export default function PreservedFormats() {
handleDownload("pdf")} + onClick={() => handleDownload(ArchivedFormat.pdf)} className="cursor-pointer hover:opacity-60 duration-100 p-2 rounded-md" > diff --git a/lib/api/controllers/collections/collectionId/deleteCollectionById.ts b/lib/api/controllers/collections/collectionId/deleteCollectionById.ts index 792f9fa..03870aa 100644 --- a/lib/api/controllers/collections/collectionId/deleteCollectionById.ts +++ b/lib/api/controllers/collections/collectionId/deleteCollectionById.ts @@ -10,14 +10,10 @@ export default async function deleteCollection( if (!collectionId) return { response: "Please choose a valid collection.", status: 401 }; - const collectionIsAccessible = (await getPermission({ + const collectionIsAccessible = await getPermission({ userId, collectionId, - })) as - | (Collection & { - members: UsersAndCollections[]; - }) - | null; + }); const memberHasAccess = collectionIsAccessible?.members.some( (e: UsersAndCollections) => e.userId === userId diff --git a/lib/api/controllers/collections/collectionId/updateCollectionById.ts b/lib/api/controllers/collections/collectionId/updateCollectionById.ts index 30bb601..f257021 100644 --- a/lib/api/controllers/collections/collectionId/updateCollectionById.ts +++ b/lib/api/controllers/collections/collectionId/updateCollectionById.ts @@ -11,14 +11,10 @@ export default async function updateCollection( if (!collectionId) return { response: "Please choose a valid collection.", status: 401 }; - const collectionIsAccessible = (await getPermission({ + const collectionIsAccessible = await getPermission({ userId, collectionId, - })) as - | (Collection & { - members: UsersAndCollections[]; - }) - | null; + }); if (!(collectionIsAccessible?.ownerId === userId)) return { response: "Collection is not accessible.", status: 401 }; diff --git a/lib/api/controllers/links/linkId/deleteLinkById.ts b/lib/api/controllers/links/linkId/deleteLinkById.ts index 8a56656..90adba4 100644 --- a/lib/api/controllers/links/linkId/deleteLinkById.ts +++ b/lib/api/controllers/links/linkId/deleteLinkById.ts @@ -6,11 +6,7 @@ import removeFile from "@/lib/api/storage/removeFile"; export default async function deleteLink(userId: number, linkId: number) { if (!linkId) return { response: "Please choose a valid link.", status: 401 }; - const collectionIsAccessible = (await getPermission({ userId, linkId })) as - | (Collection & { - members: UsersAndCollections[]; - }) - | null; + const collectionIsAccessible = await getPermission({ userId, linkId }); const memberHasAccess = collectionIsAccessible?.members.some( (e: UsersAndCollections) => e.userId === userId && e.canDelete diff --git a/lib/api/controllers/links/linkId/getLinkById.ts b/lib/api/controllers/links/linkId/getLinkById.ts index bf872c2..e344ae4 100644 --- a/lib/api/controllers/links/linkId/getLinkById.ts +++ b/lib/api/controllers/links/linkId/getLinkById.ts @@ -9,11 +9,7 @@ export default async function getLinkById(userId: number, linkId: number) { status: 401, }; - const collectionIsAccessible = (await getPermission({ userId, linkId })) as - | (Collection & { - members: UsersAndCollections[]; - }) - | null; + const collectionIsAccessible = await getPermission({ userId, linkId }); const memberHasAccess = collectionIsAccessible?.members.some( (e: UsersAndCollections) => e.userId === userId diff --git a/lib/api/controllers/links/linkId/updateLinkById.ts b/lib/api/controllers/links/linkId/updateLinkById.ts index 41fb54b..7f7fb2e 100644 --- a/lib/api/controllers/links/linkId/updateLinkById.ts +++ b/lib/api/controllers/links/linkId/updateLinkById.ts @@ -15,11 +15,7 @@ export default async function updateLinkById( status: 401, }; - const collectionIsAccessible = (await getPermission({ userId, linkId })) as - | (Collection & { - members: UsersAndCollections[]; - }) - | null; + const collectionIsAccessible = await getPermission({ userId, linkId }); const memberHasAccess = collectionIsAccessible?.members.some( (e: UsersAndCollections) => e.userId === userId && e.canUpdate diff --git a/lib/api/controllers/links/postLink.ts b/lib/api/controllers/links/postLink.ts index a147814..b93eeef 100644 --- a/lib/api/controllers/links/postLink.ts +++ b/lib/api/controllers/links/postLink.ts @@ -2,7 +2,7 @@ import { prisma } from "@/lib/api/db"; import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; import getTitle from "@/lib/api/getTitle"; import archive from "@/lib/api/archive"; -import { Collection, UsersAndCollections } from "@prisma/client"; +import { UsersAndCollections } from "@prisma/client"; import getPermission from "@/lib/api/getPermission"; import createFolder from "@/lib/api/storage/createFolder"; @@ -27,14 +27,10 @@ export default async function postLink( link.collection.name = link.collection.name.trim(); if (link.collection.id) { - const collectionIsAccessible = (await getPermission({ + const collectionIsAccessible = await getPermission({ userId, collectionId: link.collection.id, - })) as - | (Collection & { - members: UsersAndCollections[]; - }) - | null; + }); const memberHasAccess = collectionIsAccessible?.members.some( (e: UsersAndCollections) => e.userId === userId && e.canCreate diff --git a/lib/api/getPermission.ts b/lib/api/getPermission.ts index cefd4e1..61dc5c5 100644 --- a/lib/api/getPermission.ts +++ b/lib/api/getPermission.ts @@ -1,35 +1,24 @@ import { prisma } from "@/lib/api/db"; type Props = { - userId?: number; + userId: number; collectionId?: number; linkId?: number; - isPublic?: boolean; }; export default async function getPermission({ userId, collectionId, linkId, - isPublic, }: Props) { if (linkId) { const check = await prisma.collection.findFirst({ where: { - [isPublic ? "OR" : "AND"]: [ - { - id: collectionId, - OR: [{ ownerId: userId }, { members: { some: { userId } } }], - links: { - some: { - id: linkId, - }, - }, + links: { + some: { + id: linkId, }, - { - isPublic: isPublic ? true : undefined, - }, - ], + }, }, include: { members: true }, }); @@ -38,15 +27,8 @@ export default async function getPermission({ } else if (collectionId) { const check = await prisma.collection.findFirst({ where: { - [isPublic ? "OR" : "AND"]: [ - { - id: collectionId, - OR: [{ ownerId: userId }, { members: { some: { userId } } }], - }, - { - isPublic: isPublic ? true : undefined, - }, - ], + id: collectionId, + OR: [{ ownerId: userId }, { members: { some: { userId } } }], }, include: { members: true }, }); diff --git a/pages/api/v1/archives/[...params].ts b/pages/api/v1/archives/[...params].ts deleted file mode 100644 index 065ba2f..0000000 --- a/pages/api/v1/archives/[...params].ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; -import getPermission from "@/lib/api/getPermission"; -import readFile from "@/lib/api/storage/readFile"; -import { getToken } from "next-auth/jwt"; - -export default async function Index(req: NextApiRequest, res: NextApiResponse) { - if (!req.query.params) - return res.status(401).json({ response: "Invalid parameters." }); - - const token = await getToken({ req }); - const userId = token?.id; - - const collectionId = req.query.params[0]; - const linkId = req.query.params[1]; - - const collectionIsAccessible = await getPermission({ - userId, - collectionId: Number(collectionId), - }); - - if (!collectionIsAccessible) - return res - .status(401) - .json({ response: "You don't have access to this collection." }); - - const { file, contentType, status } = await readFile( - `archives/${collectionId}/${linkId}` - ); - res.setHeader("Content-Type", contentType).status(status as number); - - return res.send(file); -} diff --git a/pages/api/v1/archives/[linkId].ts b/pages/api/v1/archives/[linkId].ts new file mode 100644 index 0000000..74f7738 --- /dev/null +++ b/pages/api/v1/archives/[linkId].ts @@ -0,0 +1,49 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import readFile from "@/lib/api/storage/readFile"; +import { getToken } from "next-auth/jwt"; +import { prisma } from "@/lib/api/db"; +import { ArchivedFormat } from "@/types/global"; + +export default async function Index(req: NextApiRequest, res: NextApiResponse) { + const linkId = Number(req.query.linkId); + const format = Number(req.query.format); + + let suffix; + + if (format === ArchivedFormat.screenshot) suffix = ".png"; + else if (format === ArchivedFormat.pdf) suffix = ".pdf"; + else if (format === ArchivedFormat.readability) suffix = "_readability.json"; + + if (!linkId || !suffix) + return res.status(401).json({ response: "Invalid parameters." }); + + const token = await getToken({ req }); + const userId = 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." }); + + const { file, contentType, status } = await readFile( + `archives/${collectionIsAccessible.id}/${linkId + suffix}` + ); + res.setHeader("Content-Type", contentType).status(status as number); + + return res.send(file); +} diff --git a/pages/links/[id].tsx b/pages/links/[id].tsx index 6f18235..615f26b 100644 --- a/pages/links/[id].tsx +++ b/pages/links/[id].tsx @@ -3,7 +3,10 @@ import React, { useEffect, useState } from "react"; import Link from "next/link"; import useLinkStore from "@/store/links"; import { useRouter } from "next/router"; -import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; +import { + ArchivedFormat, + LinkIncludingShortenedCollectionAndTags, +} from "@/types/global"; import Image from "next/image"; import ColorThief, { RGBColor } from "colorthief"; import { useTheme } from "next-themes"; @@ -63,7 +66,9 @@ export default function Index() { link?.readabilityPath && link?.readabilityPath !== "pending" ) { - const response = await fetch(`/api/v1/${link?.readabilityPath}`); + const response = await fetch( + `/api/v1/archives/${link?.id}?format=${ArchivedFormat.readability}` + ); const data = await response?.json(); diff --git a/pages/public/links/[id].tsx b/pages/public/links/[id].tsx index d95c6b8..ea01de3 100644 --- a/pages/public/links/[id].tsx +++ b/pages/public/links/[id].tsx @@ -3,7 +3,10 @@ import React, { useEffect, useState } from "react"; import Link from "next/link"; import useLinkStore from "@/store/links"; import { useRouter } from "next/router"; -import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; +import { + ArchivedFormat, + LinkIncludingShortenedCollectionAndTags, +} from "@/types/global"; import Image from "next/image"; import ColorThief, { RGBColor } from "colorthief"; import { useTheme } from "next-themes"; @@ -64,7 +67,9 @@ export default function Index() { link?.readabilityPath && link?.readabilityPath !== "pending" ) { - const response = await fetch(`/api/v1/${link?.readabilityPath}`); + const response = await fetch( + `/api/v1/archives/${link?.id}?format=${ArchivedFormat.readability}` + ); const data = await response?.json(); diff --git a/types/global.ts b/types/global.ts index 2af3766..a423d45 100644 --- a/types/global.ts +++ b/types/global.ts @@ -115,3 +115,9 @@ export type DeleteUserBody = { feedback?: Stripe.SubscriptionCancelParams.CancellationDetails.Feedback; }; }; + +export enum ArchivedFormat { + screenshot, + pdf, + readability, +}