diff --git a/.env.sample b/.env.sample index b7161ae..2993c39 100644 --- a/.env.sample +++ b/.env.sample @@ -21,6 +21,7 @@ BROWSER_TIMEOUT= IGNORE_UNAUTHORIZED_CA= IGNORE_HTTPS_ERRORS= IGNORE_URL_SIZE_LIMIT= +DEMO_MODE= NEXT_PUBLIC_ADMIN= NEXT_PUBLIC_MAX_FILE_BUFFER= MONOLITH_MAX_BUFFER= diff --git a/components/LinkListOptions.tsx b/components/LinkListOptions.tsx index 880e91d..842363e 100644 --- a/components/LinkListOptions.tsx +++ b/components/LinkListOptions.tsx @@ -81,12 +81,15 @@ const LinkListOptions = ({ toast.dismiss(load); - response.ok && + if (response.ok) { toast.success( selectedLinks.length === 1 ? t("link_deleted") : t("links_deleted", { count: selectedLinks.length }) ); + } else { + toast.error(response.data as string); + } }; return ( diff --git a/components/LinkViews/LinkComponents/LinkActions.tsx b/components/LinkViews/LinkComponents/LinkActions.tsx index 46aff1d..c68dd06 100644 --- a/components/LinkViews/LinkComponents/LinkActions.tsx +++ b/components/LinkViews/LinkComponents/LinkActions.tsx @@ -55,8 +55,11 @@ export default function LinkActions({ toast.dismiss(load); - response.ok && + if (response.ok) { toast.success(isAlreadyPinned ? t("link_unpinned") : t("link_unpinned")); + } else { + toast.error(response.data as string); + } }; const deleteLink = async () => { @@ -66,7 +69,11 @@ export default function LinkActions({ toast.dismiss(load); - response.ok && toast.success(t("deleted")); + if (response.ok) { + toast.success(t("deleted")); + } else { + toast.error(response.data as string); + } }; return ( diff --git a/components/LinkViews/LinkList.tsx b/components/LinkViews/LinkList.tsx index 0f944d8..723c47e 100644 --- a/components/LinkViews/LinkList.tsx +++ b/components/LinkViews/LinkList.tsx @@ -157,7 +157,12 @@ export default function LinkCardCompact({ // linkInfo={showInfo} /> -
+ > ); } diff --git a/components/ModalContent/DeleteLinkModal.tsx b/components/ModalContent/DeleteLinkModal.tsx index 92de104..192dfb7 100644 --- a/components/ModalContent/DeleteLinkModal.tsx +++ b/components/ModalContent/DeleteLinkModal.tsx @@ -30,7 +30,11 @@ export default function DeleteLinkModal({ onClose, activeLink }: Props) { toast.dismiss(load); - response.ok && toast.success(t("deleted")); + if (response.ok) { + toast.success(t("deleted")); + } else { + toast.error(response.data as string); + } if (router.pathname.startsWith("/links/[id]")) { router.push("/dashboard"); diff --git a/components/ModalContent/DeleteUserModal.tsx b/components/ModalContent/DeleteUserModal.tsx index 73a81ed..6dc7ee2 100644 --- a/components/ModalContent/DeleteUserModal.tsx +++ b/components/ModalContent/DeleteUserModal.tsx @@ -20,7 +20,11 @@ export default function DeleteUserModal({ onClose, userId }: Props) { toast.dismiss(load); - response.ok && toast.success(t("user_deleted")); + if (response.ok) { + toast.success(t("user_deleted")); + } else { + toast.error(response.data as string); + } onClose(); }; diff --git a/components/ModalContent/RevokeTokenModal.tsx b/components/ModalContent/RevokeTokenModal.tsx index 9342a3a..6b741e6 100644 --- a/components/ModalContent/RevokeTokenModal.tsx +++ b/components/ModalContent/RevokeTokenModal.tsx @@ -30,6 +30,8 @@ export default function DeleteTokenModal({ onClose, activeToken }: Props) { if (response.ok) { toast.success(t("token_revoked")); + } else { + toast.error(response.data as string); } onClose(); diff --git a/components/PageHeader.tsx b/components/PageHeader.tsx index 6af9a25..d4f7850 100644 --- a/components/PageHeader.tsx +++ b/components/PageHeader.tsx @@ -12,7 +12,7 @@ export default function PageHeader({ return ({title}
diff --git a/pages/api/v1/archives/[linkId].ts b/pages/api/v1/archives/[linkId].ts index faa3dd2..cf044b1 100644 --- a/pages/api/v1/archives/[linkId].ts +++ b/pages/api/v1/archives/[linkId].ts @@ -77,6 +77,12 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) { return res.send(file); } } else if (req.method === "POST") { + if (process.env.DEMO_MODE === "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; @@ -86,14 +92,18 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) { }); if (!collectionPermissions) - return { response: "Collection is not accessible.", status: 400 }; + 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 { response: "Collection is not accessible.", status: 400 }; + return res.status(400).json({ + response: "Collection is not accessible.", + }); // await uploadHandler(linkId, ) @@ -108,10 +118,10 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) { }); if (numberOfLinksTheUserHas > MAX_LINKS_PER_USER) - return { - response: `Each collection owner can only have a maximum of ${MAX_LINKS_PER_USER} Links.`, - status: 400, - }; + 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 diff --git a/pages/api/v1/auth/forgot-password.ts b/pages/api/v1/auth/forgot-password.ts index 0672914..1728365 100644 --- a/pages/api/v1/auth/forgot-password.ts +++ b/pages/api/v1/auth/forgot-password.ts @@ -7,6 +7,12 @@ export default async function forgotPassword( res: NextApiResponse ) { if (req.method === "POST") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const email = req.body.email; if (!email) { diff --git a/pages/api/v1/auth/reset-password.ts b/pages/api/v1/auth/reset-password.ts index 14287f3..7199fcb 100644 --- a/pages/api/v1/auth/reset-password.ts +++ b/pages/api/v1/auth/reset-password.ts @@ -7,6 +7,12 @@ export default async function resetPassword( res: NextApiResponse ) { if (req.method === "POST") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const token = req.body.token; const password = req.body.password; diff --git a/pages/api/v1/auth/verify-email.ts b/pages/api/v1/auth/verify-email.ts index 2621c1e..c19521d 100644 --- a/pages/api/v1/auth/verify-email.ts +++ b/pages/api/v1/auth/verify-email.ts @@ -7,6 +7,12 @@ export default async function verifyEmail( res: NextApiResponse ) { if (req.method === "POST") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const token = req.query.token; if (!token || typeof token !== "string") { diff --git a/pages/api/v1/collections/[id].ts b/pages/api/v1/collections/[id].ts index 637e10f..994dd33 100644 --- a/pages/api/v1/collections/[id].ts +++ b/pages/api/v1/collections/[id].ts @@ -19,9 +19,21 @@ export default async function collections( .status(collections.status) .json({ response: collections.response }); } else if (req.method === "PUT") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const updated = await updateCollectionById(user.id, collectionId, req.body); return res.status(updated.status).json({ response: updated.response }); } else if (req.method === "DELETE") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const deleted = await deleteCollectionById(user.id, collectionId); return res.status(deleted.status).json({ response: deleted.response }); } diff --git a/pages/api/v1/collections/index.ts b/pages/api/v1/collections/index.ts index 3b229dd..369863f 100644 --- a/pages/api/v1/collections/index.ts +++ b/pages/api/v1/collections/index.ts @@ -16,6 +16,12 @@ export default async function collections( .status(collections.status) .json({ response: collections.response }); } else if (req.method === "POST") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const newCollection = await postCollection(req.body, user.id); return res .status(newCollection.status) diff --git a/pages/api/v1/links/[id]/archive/index.ts b/pages/api/v1/links/[id]/archive/index.ts index ef91a4e..fa6cf83 100644 --- a/pages/api/v1/links/[id]/archive/index.ts +++ b/pages/api/v1/links/[id]/archive/index.ts @@ -29,6 +29,12 @@ export default async function links(req: NextApiRequest, res: NextApiResponse) { }); if (req.method === "PUT") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + if ( link?.lastPreserved && getTimezoneDifferenceInMinutes(new Date(), link?.lastPreserved) < diff --git a/pages/api/v1/links/[id]/index.ts b/pages/api/v1/links/[id]/index.ts index 146ecb2..02299f4 100644 --- a/pages/api/v1/links/[id]/index.ts +++ b/pages/api/v1/links/[id]/index.ts @@ -14,6 +14,12 @@ export default async function links(req: NextApiRequest, res: NextApiResponse) { response: updated.response, }); } else if (req.method === "PUT") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const updated = await updateLinkById( user.id, Number(req.query.id), @@ -23,6 +29,12 @@ export default async function links(req: NextApiRequest, res: NextApiResponse) { response: updated.response, }); } else if (req.method === "DELETE") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const deleted = await deleteLinkById(user.id, Number(req.query.id)); return res.status(deleted.status).json({ response: deleted.response, diff --git a/pages/api/v1/links/index.ts b/pages/api/v1/links/index.ts index 35b0243..f506d3b 100644 --- a/pages/api/v1/links/index.ts +++ b/pages/api/v1/links/index.ts @@ -37,11 +37,23 @@ export default async function links(req: NextApiRequest, res: NextApiResponse) { const links = await getLinks(user.id, convertedData); return res.status(links.status).json({ response: links.response }); } else if (req.method === "POST") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const newlink = await postLink(req.body, user.id); return res.status(newlink.status).json({ response: newlink.response, }); } else if (req.method === "PUT") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const updated = await updateLinks( user.id, req.body.links, @@ -52,6 +64,12 @@ export default async function links(req: NextApiRequest, res: NextApiResponse) { response: updated.response, }); } else if (req.method === "DELETE") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const deleted = await deleteLinksById(user.id, req.body.linkIds); return res.status(deleted.status).json({ response: deleted.response, diff --git a/pages/api/v1/migration/index.ts b/pages/api/v1/migration/index.ts index 8cdf5db..60f52a7 100644 --- a/pages/api/v1/migration/index.ts +++ b/pages/api/v1/migration/index.ts @@ -30,6 +30,12 @@ export default async function users(req: NextApiRequest, res: NextApiResponse) { .status(data.status) .json(data.response); } else if (req.method === "POST") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const request: MigrationRequest = JSON.parse(req.body); let data; diff --git a/pages/api/v1/tags/[id].ts b/pages/api/v1/tags/[id].ts index d82b1f7..f9a9fd8 100644 --- a/pages/api/v1/tags/[id].ts +++ b/pages/api/v1/tags/[id].ts @@ -10,9 +10,21 @@ export default async function tags(req: NextApiRequest, res: NextApiResponse) { const tagId = Number(req.query.id); if (req.method === "PUT") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const tags = await updeteTagById(user.id, tagId, req.body); return res.status(tags.status).json({ response: tags.response }); } else if (req.method === "DELETE") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const tags = await deleteTagById(user.id, tagId); return res.status(tags.status).json({ response: tags.response }); } diff --git a/pages/api/v1/tokens/[id].ts b/pages/api/v1/tokens/[id].ts index 6c2e51b..a0615d7 100644 --- a/pages/api/v1/tokens/[id].ts +++ b/pages/api/v1/tokens/[id].ts @@ -7,6 +7,12 @@ export default async function token(req: NextApiRequest, res: NextApiResponse) { if (!user) return; if (req.method === "DELETE") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const deleted = await deleteToken(user.id, Number(req.query.id) as number); return res.status(deleted.status).json({ response: deleted.response }); } diff --git a/pages/api/v1/tokens/index.ts b/pages/api/v1/tokens/index.ts index 8af63fb..915980f 100644 --- a/pages/api/v1/tokens/index.ts +++ b/pages/api/v1/tokens/index.ts @@ -11,6 +11,12 @@ export default async function tokens( if (!user) return; if (req.method === "POST") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const token = await postToken(JSON.parse(req.body), user.id); return res.status(token.status).json({ response: token.response }); } else if (req.method === "GET") { diff --git a/pages/api/v1/users/[id].ts b/pages/api/v1/users/[id].ts index de8ef56..9e102f9 100644 --- a/pages/api/v1/users/[id].ts +++ b/pages/api/v1/users/[id].ts @@ -58,9 +58,21 @@ export default async function users(req: NextApiRequest, res: NextApiResponse) { } if (req.method === "PUT") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const updated = await updateUserById(userId, req.body); return res.status(updated.status).json({ response: updated.response }); } else if (req.method === "DELETE") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const updated = await deleteUserById(userId, req.body, isServerAdmin); return res.status(updated.status).json({ response: updated.response }); } diff --git a/pages/api/v1/users/index.ts b/pages/api/v1/users/index.ts index c94e9f2..8f1f770 100644 --- a/pages/api/v1/users/index.ts +++ b/pages/api/v1/users/index.ts @@ -5,6 +5,12 @@ import verifyUser from "@/lib/api/verifyUser"; export default async function users(req: NextApiRequest, res: NextApiResponse) { if (req.method === "POST") { + if (process.env.DEMO_MODE === "true") + return res.status(400).json({ + response: + "This action is disabled because this is a read-only demo of Linkwarden.", + }); + const response = await postUser(req, res); return res.status(response.status).json({ response: response.response }); } else if (req.method === "GET") { diff --git a/pages/collections/[id].tsx b/pages/collections/[id].tsx index bdfbfe9..bd24a95 100644 --- a/pages/collections/[id].tsx +++ b/pages/collections/[id].tsx @@ -128,7 +128,7 @@ export default function Index() { style={{ color: activeCollection?.color }} > -+
{activeCollection?.name}
+
{activeTag?.name}