Merge branch 'dev' into main
This commit is contained in:
@@ -32,7 +32,11 @@ export default async function archiveHandler(link: LinksAndCollectionAndOwner) {
|
||||
}
|
||||
|
||||
const browser = await chromium.launch(browserOptions);
|
||||
const context = await browser.newContext(devices["Desktop Chrome"]);
|
||||
const context = await browser.newContext({
|
||||
...devices["Desktop Chrome"],
|
||||
ignoreHTTPSErrors: process.env.IGNORE_HTTPS_ERRORS === "true",
|
||||
});
|
||||
|
||||
const page = await context.newPage();
|
||||
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
|
||||
@@ -37,6 +37,8 @@ export default async function deleteCollection(
|
||||
}
|
||||
|
||||
const deletedCollection = await prisma.$transaction(async () => {
|
||||
await deleteSubCollections(collectionId);
|
||||
|
||||
await prisma.usersAndCollections.deleteMany({
|
||||
where: {
|
||||
collection: {
|
||||
@@ -53,7 +55,7 @@ export default async function deleteCollection(
|
||||
},
|
||||
});
|
||||
|
||||
removeFolder({ filePath: `archives/${collectionId}` });
|
||||
await removeFolder({ filePath: `archives/${collectionId}` });
|
||||
|
||||
return await prisma.collection.delete({
|
||||
where: {
|
||||
@@ -64,3 +66,35 @@ export default async function deleteCollection(
|
||||
|
||||
return { response: deletedCollection, status: 200 };
|
||||
}
|
||||
|
||||
async function deleteSubCollections(collectionId: number) {
|
||||
const subCollections = await prisma.collection.findMany({
|
||||
where: { parentId: collectionId },
|
||||
});
|
||||
|
||||
for (const subCollection of subCollections) {
|
||||
await deleteSubCollections(subCollection.id);
|
||||
|
||||
await prisma.usersAndCollections.deleteMany({
|
||||
where: {
|
||||
collection: {
|
||||
id: subCollection.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.link.deleteMany({
|
||||
where: {
|
||||
collection: {
|
||||
id: subCollection.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.collection.delete({
|
||||
where: { id: subCollection.id },
|
||||
});
|
||||
|
||||
await removeFolder({ filePath: `archives/${subCollection.id}` });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { prisma } from "@/lib/api/db";
|
||||
|
||||
export default async function getCollectionById(
|
||||
userId: number,
|
||||
collectionId: number
|
||||
) {
|
||||
const collections = await prisma.collection.findFirst({
|
||||
where: {
|
||||
id: collectionId,
|
||||
OR: [
|
||||
{ ownerId: userId },
|
||||
{ members: { some: { user: { id: userId } } } },
|
||||
],
|
||||
},
|
||||
include: {
|
||||
_count: {
|
||||
select: { links: true },
|
||||
},
|
||||
members: {
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
username: true,
|
||||
name: true,
|
||||
image: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return { response: collections, status: 200 };
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { prisma } from "@/lib/api/db";
|
||||
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
|
||||
import getPermission from "@/lib/api/getPermission";
|
||||
import { Collection, UsersAndCollections } from "@prisma/client";
|
||||
|
||||
export default async function updateCollection(
|
||||
userId: number,
|
||||
@@ -19,6 +18,26 @@ export default async function updateCollection(
|
||||
if (!(collectionIsAccessible?.ownerId === userId))
|
||||
return { response: "Collection is not accessible.", status: 401 };
|
||||
|
||||
if (data.parentId) {
|
||||
const findParentCollection = await prisma.collection.findUnique({
|
||||
where: {
|
||||
id: data.parentId,
|
||||
},
|
||||
select: {
|
||||
ownerId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
findParentCollection?.ownerId !== userId ||
|
||||
typeof data.parentId !== "number"
|
||||
)
|
||||
return {
|
||||
response: "You are not authorized to create a sub-collection here.",
|
||||
status: 403,
|
||||
};
|
||||
}
|
||||
|
||||
const updatedCollection = await prisma.$transaction(async () => {
|
||||
await prisma.usersAndCollections.deleteMany({
|
||||
where: {
|
||||
@@ -32,12 +51,18 @@ export default async function updateCollection(
|
||||
where: {
|
||||
id: collectionId,
|
||||
},
|
||||
|
||||
data: {
|
||||
name: data.name.trim(),
|
||||
description: data.description,
|
||||
color: data.color,
|
||||
isPublic: data.isPublic,
|
||||
parent: data.parentId
|
||||
? {
|
||||
connect: {
|
||||
id: data.parentId,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
members: {
|
||||
create: data.members.map((e) => ({
|
||||
user: { connect: { id: e.user.id || e.userId } },
|
||||
|
||||
@@ -12,6 +12,26 @@ export default async function postCollection(
|
||||
status: 400,
|
||||
};
|
||||
|
||||
if (collection.parentId) {
|
||||
const findParentCollection = await prisma.collection.findUnique({
|
||||
where: {
|
||||
id: collection.parentId,
|
||||
},
|
||||
select: {
|
||||
ownerId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
findParentCollection?.ownerId !== userId ||
|
||||
typeof collection.parentId !== "number"
|
||||
)
|
||||
return {
|
||||
response: "You are not authorized to create a sub-collection here.",
|
||||
status: 403,
|
||||
};
|
||||
}
|
||||
|
||||
const findCollection = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: userId,
|
||||
@@ -28,7 +48,10 @@ export default async function postCollection(
|
||||
const checkIfCollectionExists = findCollection?.collections[0];
|
||||
|
||||
if (checkIfCollectionExists)
|
||||
return { response: "Collection already exists.", status: 400 };
|
||||
return {
|
||||
response: "Oops! There's already a Collection with that name.",
|
||||
status: 400,
|
||||
};
|
||||
|
||||
const newCollection = await prisma.collection.create({
|
||||
data: {
|
||||
@@ -40,6 +63,13 @@ export default async function postCollection(
|
||||
name: collection.name.trim(),
|
||||
description: collection.description,
|
||||
color: collection.color,
|
||||
parent: collection.parentId
|
||||
? {
|
||||
connect: {
|
||||
id: collection.parentId,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
include: {
|
||||
_count: {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { prisma } from "@/lib/api/db";
|
||||
import { UsersAndCollections } from "@prisma/client";
|
||||
import getPermission from "@/lib/api/getPermission";
|
||||
import removeFile from "@/lib/api/storage/removeFile";
|
||||
|
||||
export default async function deleteLinksById(
|
||||
userId: number,
|
||||
linkIds: number[]
|
||||
) {
|
||||
if (!linkIds || linkIds.length === 0) {
|
||||
return { response: "Please choose valid links.", status: 401 };
|
||||
}
|
||||
|
||||
const collectionIsAccessibleArray = [];
|
||||
|
||||
// Check if the user has access to the collection of each link
|
||||
// if any of the links are not accessible, return an error
|
||||
// if all links are accessible, continue with the deletion
|
||||
// and add the collection to the collectionIsAccessibleArray
|
||||
for (const linkId of linkIds) {
|
||||
const collectionIsAccessible = await getPermission({ userId, linkId });
|
||||
|
||||
const memberHasAccess = collectionIsAccessible?.members.some(
|
||||
(e: UsersAndCollections) => e.userId === userId && e.canDelete
|
||||
);
|
||||
|
||||
if (!(collectionIsAccessible?.ownerId === userId || memberHasAccess)) {
|
||||
return { response: "Collection is not accessible.", status: 401 };
|
||||
}
|
||||
|
||||
collectionIsAccessibleArray.push(collectionIsAccessible);
|
||||
}
|
||||
|
||||
const deletedLinks = await prisma.link.deleteMany({
|
||||
where: {
|
||||
id: { in: linkIds },
|
||||
},
|
||||
});
|
||||
|
||||
// Loop through each link and delete the associated files
|
||||
// if the user has access to the collection
|
||||
for (let i = 0; i < linkIds.length; i++) {
|
||||
const linkId = linkIds[i];
|
||||
const collectionIsAccessible = collectionIsAccessibleArray[i];
|
||||
|
||||
removeFile({
|
||||
filePath: `archives/${collectionIsAccessible?.id}/${linkId}.pdf`,
|
||||
});
|
||||
removeFile({
|
||||
filePath: `archives/${collectionIsAccessible?.id}/${linkId}.png`,
|
||||
});
|
||||
removeFile({
|
||||
filePath: `archives/${collectionIsAccessible?.id}/${linkId}_readability.json`,
|
||||
});
|
||||
}
|
||||
|
||||
return { response: deletedLinks, status: 200 };
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { LinkIncludingShortenedCollectionAndTags } from "@/types/global";
|
||||
import updateLinkById from "../linkId/updateLinkById";
|
||||
|
||||
export default async function updateLinks(
|
||||
userId: number,
|
||||
links: LinkIncludingShortenedCollectionAndTags[],
|
||||
removePreviousTags: boolean,
|
||||
newData: Pick<
|
||||
LinkIncludingShortenedCollectionAndTags,
|
||||
"tags" | "collectionId"
|
||||
>
|
||||
) {
|
||||
let allUpdatesSuccessful = true;
|
||||
|
||||
// Have to use a loop here rather than updateMany, see the following:
|
||||
// https://github.com/prisma/prisma/issues/3143
|
||||
for (const link of links) {
|
||||
let updatedTags = [...link.tags, ...(newData.tags ?? [])];
|
||||
|
||||
if (removePreviousTags) {
|
||||
// If removePreviousTags is true, replace the existing tags with new tags
|
||||
updatedTags = [...(newData.tags ?? [])];
|
||||
}
|
||||
|
||||
const updatedData: LinkIncludingShortenedCollectionAndTags = {
|
||||
...link,
|
||||
tags: updatedTags,
|
||||
collection: {
|
||||
...link.collection,
|
||||
id: newData.collectionId ?? link.collection.id,
|
||||
},
|
||||
};
|
||||
|
||||
const updatedLink = await updateLinkById(
|
||||
userId,
|
||||
link.id as number,
|
||||
updatedData
|
||||
);
|
||||
|
||||
if (updatedLink.status !== 200) {
|
||||
allUpdatesSuccessful = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allUpdatesSuccessful) {
|
||||
return { response: "All links updated successfully", status: 200 };
|
||||
} else {
|
||||
return { response: "Some links failed to update", status: 400 };
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { prisma } from "@/lib/api/db";
|
||||
import { Collection, Link, UsersAndCollections } from "@prisma/client";
|
||||
import { Link, UsersAndCollections } from "@prisma/client";
|
||||
import getPermission from "@/lib/api/getPermission";
|
||||
import removeFile from "@/lib/api/storage/removeFile";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { prisma } from "@/lib/api/db";
|
||||
import { LinkIncludingShortenedCollectionAndTags } from "@/types/global";
|
||||
import { Collection, Link, UsersAndCollections } from "@prisma/client";
|
||||
import { UsersAndCollections } from "@prisma/client";
|
||||
import getPermission from "@/lib/api/getPermission";
|
||||
import moveFile from "@/lib/api/storage/moveFile";
|
||||
|
||||
@@ -16,6 +16,10 @@ export default async function updateLinkById(
|
||||
};
|
||||
|
||||
const collectionIsAccessible = await getPermission({ userId, linkId });
|
||||
const targetCollectionIsAccessible = await getPermission({
|
||||
userId,
|
||||
collectionId: data.collection.id,
|
||||
});
|
||||
|
||||
const memberHasAccess = collectionIsAccessible?.members.some(
|
||||
(e: UsersAndCollections) => e.userId === userId && e.canUpdate
|
||||
@@ -25,9 +29,61 @@ export default async function updateLinkById(
|
||||
collectionIsAccessible?.ownerId === data.collection.ownerId &&
|
||||
data.collection.ownerId === userId;
|
||||
|
||||
const targetCollectionsAccessible =
|
||||
targetCollectionIsAccessible?.ownerId === userId;
|
||||
|
||||
const targetCollectionMatchesData = data.collection.id
|
||||
? data.collection.id === targetCollectionIsAccessible?.id
|
||||
: true && data.collection.name
|
||||
? data.collection.name === targetCollectionIsAccessible?.name
|
||||
: true && data.collection.ownerId
|
||||
? data.collection.ownerId === targetCollectionIsAccessible?.ownerId
|
||||
: true;
|
||||
|
||||
if (!targetCollectionsAccessible)
|
||||
return {
|
||||
response: "Target collection is not accessible.",
|
||||
status: 401,
|
||||
};
|
||||
else if (!targetCollectionMatchesData)
|
||||
return {
|
||||
response: "Target collection does not match the data.",
|
||||
status: 401,
|
||||
};
|
||||
|
||||
const unauthorizedSwitchCollection =
|
||||
!isCollectionOwner && collectionIsAccessible?.id !== data.collection.id;
|
||||
|
||||
const canPinPermission = collectionIsAccessible?.members.some(
|
||||
(e: UsersAndCollections) => e.userId === userId
|
||||
);
|
||||
|
||||
// If the user is able to create a link, they can pin it to their dashboard only.
|
||||
if (canPinPermission) {
|
||||
const updatedLink = await prisma.link.update({
|
||||
where: {
|
||||
id: linkId,
|
||||
},
|
||||
data: {
|
||||
pinnedBy:
|
||||
data?.pinnedBy && data.pinnedBy[0]
|
||||
? { connect: { id: userId } }
|
||||
: { disconnect: { id: userId } },
|
||||
},
|
||||
include: {
|
||||
collection: true,
|
||||
pinnedBy: isCollectionOwner
|
||||
? {
|
||||
where: { id: userId },
|
||||
select: { id: true },
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
return { response: updatedLink, status: 200 };
|
||||
}
|
||||
|
||||
// Makes sure collection members (non-owners) cannot move a link to/from a collection.
|
||||
if (unauthorizedSwitchCollection)
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { prisma } from "@/lib/api/db";
|
||||
import createFolder from "@/lib/api/storage/createFolder";
|
||||
import { JSDOM } from "jsdom";
|
||||
import { parse, Node, Element, TextNode } from "himalaya";
|
||||
|
||||
const MAX_LINKS_PER_USER = Number(process.env.MAX_LINKS_PER_USER) || 30000;
|
||||
|
||||
@@ -11,6 +12,11 @@ export default async function importFromHTMLFile(
|
||||
const dom = new JSDOM(rawData);
|
||||
const document = dom.window.document;
|
||||
|
||||
// remove bad tags
|
||||
document.querySelectorAll("meta").forEach((e) => (e.outerHTML = e.innerHTML));
|
||||
document.querySelectorAll("META").forEach((e) => (e.outerHTML = e.innerHTML));
|
||||
document.querySelectorAll("P").forEach((e) => (e.outerHTML = e.innerHTML));
|
||||
|
||||
const bookmarks = document.querySelectorAll("A");
|
||||
const totalImports = bookmarks.length;
|
||||
|
||||
@@ -28,94 +34,165 @@ export default async function importFromHTMLFile(
|
||||
status: 400,
|
||||
};
|
||||
|
||||
const folders = document.querySelectorAll("H3");
|
||||
const jsonData = parse(document.documentElement.outerHTML);
|
||||
|
||||
await prisma
|
||||
.$transaction(
|
||||
async () => {
|
||||
// @ts-ignore
|
||||
for (const folder of folders) {
|
||||
const findCollection = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
select: {
|
||||
collections: {
|
||||
where: {
|
||||
name: folder.textContent.trim(),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const checkIfCollectionExists = findCollection?.collections[0];
|
||||
|
||||
let collectionId = findCollection?.collections[0]?.id;
|
||||
|
||||
if (!checkIfCollectionExists || !collectionId) {
|
||||
const newCollection = await prisma.collection.create({
|
||||
data: {
|
||||
name: folder.textContent.trim(),
|
||||
description: "",
|
||||
color: "#0ea5e9",
|
||||
isPublic: false,
|
||||
ownerId: userId,
|
||||
},
|
||||
});
|
||||
|
||||
createFolder({ filePath: `archives/${newCollection.id}` });
|
||||
|
||||
collectionId = newCollection.id;
|
||||
}
|
||||
|
||||
createFolder({ filePath: `archives/${collectionId}` });
|
||||
|
||||
const bookmarks = folder.nextElementSibling.querySelectorAll("A");
|
||||
for (const bookmark of bookmarks) {
|
||||
await prisma.link.create({
|
||||
data: {
|
||||
name: bookmark.textContent.trim(),
|
||||
url: bookmark.getAttribute("HREF"),
|
||||
tags: bookmark.getAttribute("TAGS")
|
||||
? {
|
||||
connectOrCreate: bookmark
|
||||
.getAttribute("TAGS")
|
||||
.split(",")
|
||||
.map((tag: string) =>
|
||||
tag
|
||||
? {
|
||||
where: {
|
||||
name_ownerId: {
|
||||
name: tag.trim(),
|
||||
ownerId: userId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
name: tag.trim(),
|
||||
owner: {
|
||||
connect: {
|
||||
id: userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
description: bookmark.getAttribute("DESCRIPTION")
|
||||
? bookmark.getAttribute("DESCRIPTION")
|
||||
: "",
|
||||
collectionId: collectionId,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{ timeout: 30000 }
|
||||
)
|
||||
.catch((err) => console.log(err));
|
||||
for (const item of jsonData) {
|
||||
console.log(item);
|
||||
await processBookmarks(userId, item as Element);
|
||||
}
|
||||
|
||||
return { response: "Success.", status: 200 };
|
||||
}
|
||||
|
||||
async function processBookmarks(
|
||||
userId: number,
|
||||
data: Node,
|
||||
parentCollectionId?: number
|
||||
) {
|
||||
if (data.type === "element") {
|
||||
for (const item of data.children) {
|
||||
if (item.type === "element" && item.tagName === "dt") {
|
||||
// process collection or sub-collection
|
||||
|
||||
let collectionId;
|
||||
const collectionName = item.children.find(
|
||||
(e) => e.type === "element" && e.tagName === "h3"
|
||||
) as Element;
|
||||
|
||||
if (collectionName) {
|
||||
collectionId = await createCollection(
|
||||
userId,
|
||||
(collectionName.children[0] as TextNode).content,
|
||||
parentCollectionId
|
||||
);
|
||||
}
|
||||
await processBookmarks(
|
||||
userId,
|
||||
item,
|
||||
collectionId || parentCollectionId
|
||||
);
|
||||
} else if (item.type === "element" && item.tagName === "a") {
|
||||
// process link
|
||||
|
||||
const linkUrl = item?.attributes.find((e) => e.key === "href")?.value;
|
||||
const linkName = (
|
||||
item?.children.find((e) => e.type === "text") as TextNode
|
||||
)?.content;
|
||||
const linkTags = item?.attributes
|
||||
.find((e) => e.key === "tags")
|
||||
?.value.split(",");
|
||||
|
||||
if (linkUrl && parentCollectionId) {
|
||||
await createLink(
|
||||
userId,
|
||||
linkUrl,
|
||||
parentCollectionId,
|
||||
linkName,
|
||||
"",
|
||||
linkTags
|
||||
);
|
||||
} else if (linkUrl) {
|
||||
// create a collection named "Imported Bookmarks" and add the link to it
|
||||
const collectionId = await createCollection(userId, "Imports");
|
||||
|
||||
await createLink(
|
||||
userId,
|
||||
linkUrl,
|
||||
collectionId,
|
||||
linkName,
|
||||
"",
|
||||
linkTags
|
||||
);
|
||||
}
|
||||
|
||||
await processBookmarks(userId, item, parentCollectionId);
|
||||
} else {
|
||||
// process anything else
|
||||
await processBookmarks(userId, item, parentCollectionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const createCollection = async (
|
||||
userId: number,
|
||||
collectionName: string,
|
||||
parentId?: number
|
||||
) => {
|
||||
const findCollection = await prisma.collection.findFirst({
|
||||
where: {
|
||||
parentId,
|
||||
name: collectionName,
|
||||
ownerId: userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (findCollection) {
|
||||
return findCollection.id;
|
||||
}
|
||||
|
||||
const collectionId = await prisma.collection.create({
|
||||
data: {
|
||||
name: collectionName,
|
||||
parent: parentId
|
||||
? {
|
||||
connect: {
|
||||
id: parentId,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
owner: {
|
||||
connect: {
|
||||
id: userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
createFolder({ filePath: `archives/${collectionId.id}` });
|
||||
|
||||
return collectionId.id;
|
||||
};
|
||||
|
||||
const createLink = async (
|
||||
userId: number,
|
||||
url: string,
|
||||
collectionId: number,
|
||||
name?: string,
|
||||
description?: string,
|
||||
tags?: string[]
|
||||
) => {
|
||||
await prisma.link.create({
|
||||
data: {
|
||||
name: name || "",
|
||||
url,
|
||||
description,
|
||||
collectionId,
|
||||
tags:
|
||||
tags && tags[0]
|
||||
? {
|
||||
connectOrCreate: tags.map((tag: string) => {
|
||||
return (
|
||||
{
|
||||
where: {
|
||||
name_ownerId: {
|
||||
name: tag.trim(),
|
||||
ownerId: userId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
name: tag.trim(),
|
||||
owner: {
|
||||
connect: {
|
||||
id: userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
} || undefined
|
||||
);
|
||||
}),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { prisma } from "@/lib/api/db";
|
||||
|
||||
export default async function getToken(userId: number) {
|
||||
const getTokens = await prisma.accessToken.findMany({
|
||||
where: {
|
||||
userId,
|
||||
revoked: false,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
expires: true,
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
response: getTokens,
|
||||
status: 200,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { prisma } from "@/lib/api/db";
|
||||
import { TokenExpiry } from "@/types/global";
|
||||
import crypto from "crypto";
|
||||
import { decode, encode } from "next-auth/jwt";
|
||||
|
||||
export default async function postToken(
|
||||
body: {
|
||||
name: string;
|
||||
expires: TokenExpiry;
|
||||
},
|
||||
userId: number
|
||||
) {
|
||||
console.log(body);
|
||||
|
||||
const checkHasEmptyFields = !body.name || body.expires === undefined;
|
||||
|
||||
if (checkHasEmptyFields)
|
||||
return {
|
||||
response: "Please fill out all the fields.",
|
||||
status: 400,
|
||||
};
|
||||
|
||||
const checkIfTokenExists = await prisma.accessToken.findFirst({
|
||||
where: {
|
||||
name: body.name,
|
||||
revoked: false,
|
||||
userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (checkIfTokenExists) {
|
||||
return {
|
||||
response: "Token with that name already exists.",
|
||||
status: 400,
|
||||
};
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
let expiryDate = new Date();
|
||||
const oneDayInSeconds = 86400;
|
||||
let expiryDateSecond = 7 * oneDayInSeconds;
|
||||
|
||||
if (body.expires === TokenExpiry.oneMonth) {
|
||||
expiryDate.setDate(expiryDate.getDate() + 30);
|
||||
expiryDateSecond = 30 * oneDayInSeconds;
|
||||
} else if (body.expires === TokenExpiry.twoMonths) {
|
||||
expiryDate.setDate(expiryDate.getDate() + 60);
|
||||
expiryDateSecond = 60 * oneDayInSeconds;
|
||||
} else if (body.expires === TokenExpiry.threeMonths) {
|
||||
expiryDate.setDate(expiryDate.getDate() + 90);
|
||||
expiryDateSecond = 90 * oneDayInSeconds;
|
||||
} else if (body.expires === TokenExpiry.never) {
|
||||
expiryDate.setDate(expiryDate.getDate() + 73000); // 200 years (not really never)
|
||||
expiryDateSecond = 73050 * oneDayInSeconds;
|
||||
} else {
|
||||
expiryDate.setDate(expiryDate.getDate() + 7);
|
||||
expiryDateSecond = 7 * oneDayInSeconds;
|
||||
}
|
||||
|
||||
const token = await encode({
|
||||
token: {
|
||||
id: userId,
|
||||
iat: now / 1000,
|
||||
exp: (expiryDate as any) / 1000,
|
||||
jti: crypto.randomUUID(),
|
||||
},
|
||||
maxAge: expiryDateSecond || 604800,
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
});
|
||||
|
||||
const tokenBody = await decode({
|
||||
token,
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
});
|
||||
|
||||
const createToken = await prisma.accessToken.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
userId,
|
||||
token: tokenBody?.jti as string,
|
||||
expires: expiryDate,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
response: {
|
||||
secretKey: token,
|
||||
token: createToken,
|
||||
},
|
||||
status: 200,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { prisma } from "@/lib/api/db";
|
||||
|
||||
export default async function deleteToken(userId: number, tokenId: number) {
|
||||
if (!tokenId)
|
||||
return { response: "Please choose a valid token.", status: 401 };
|
||||
|
||||
const tokenExists = await prisma.accessToken.findFirst({
|
||||
where: {
|
||||
id: tokenId,
|
||||
userId,
|
||||
},
|
||||
});
|
||||
|
||||
const revokedToken = await prisma.accessToken.update({
|
||||
where: {
|
||||
id: tokenExists?.id,
|
||||
},
|
||||
data: {
|
||||
revoked: true,
|
||||
},
|
||||
});
|
||||
|
||||
return { response: revokedToken, status: 200 };
|
||||
}
|
||||
@@ -186,6 +186,7 @@ export default async function updateUserById(
|
||||
archiveAsScreenshot: data.archiveAsScreenshot,
|
||||
archiveAsPDF: data.archiveAsPDF,
|
||||
archiveAsWaybackMachine: data.archiveAsWaybackMachine,
|
||||
linksRouteTo: data.linksRouteTo,
|
||||
password:
|
||||
data.newPassword && data.newPassword !== ""
|
||||
? newHashedPassword
|
||||
|
||||
@@ -3,12 +3,14 @@ import { prisma } from "@/lib/api/db";
|
||||
type Props = {
|
||||
userId: number;
|
||||
collectionId?: number;
|
||||
collectionName?: string;
|
||||
linkId?: number;
|
||||
};
|
||||
|
||||
export default async function getPermission({
|
||||
userId,
|
||||
collectionId,
|
||||
collectionName,
|
||||
linkId,
|
||||
}: Props) {
|
||||
if (linkId) {
|
||||
@@ -24,10 +26,11 @@ export default async function getPermission({
|
||||
});
|
||||
|
||||
return check;
|
||||
} else if (collectionId) {
|
||||
} else if (collectionId || collectionName) {
|
||||
const check = await prisma.collection.findFirst({
|
||||
where: {
|
||||
id: collectionId,
|
||||
id: collectionId || undefined,
|
||||
name: collectionName || undefined,
|
||||
OR: [{ ownerId: userId }, { members: { some: { userId } } }],
|
||||
},
|
||||
include: { members: true },
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { NextApiRequest } from "next";
|
||||
import { JWT, getToken } from "next-auth/jwt";
|
||||
import { prisma } from "./db";
|
||||
|
||||
type Props = {
|
||||
req: NextApiRequest;
|
||||
};
|
||||
|
||||
export default async function verifyToken({
|
||||
req,
|
||||
}: Props): Promise<JWT | string> {
|
||||
const token = await getToken({ req });
|
||||
const userId = token?.id;
|
||||
|
||||
if (!userId) {
|
||||
return "You must be logged in.";
|
||||
}
|
||||
|
||||
if (token.exp < Date.now() / 1000) {
|
||||
return "Your session has expired, please log in again.";
|
||||
}
|
||||
|
||||
// check if token is revoked
|
||||
const revoked = await prisma.accessToken.findFirst({
|
||||
where: {
|
||||
token: token.jti,
|
||||
revoked: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (revoked) {
|
||||
return "Your session has expired, please log in again.";
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
import { prisma } from "./db";
|
||||
import { User } from "@prisma/client";
|
||||
import verifySubscription from "./verifySubscription";
|
||||
import verifyToken from "./verifyToken";
|
||||
|
||||
type Props = {
|
||||
req: NextApiRequest;
|
||||
@@ -15,14 +15,15 @@ export default async function verifyUser({
|
||||
req,
|
||||
res,
|
||||
}: Props): Promise<User | null> {
|
||||
const token = await getToken({ req });
|
||||
const userId = token?.id;
|
||||
const token = await verifyToken({ req });
|
||||
|
||||
if (!userId) {
|
||||
res.status(401).json({ response: "You must be logged in." });
|
||||
if (typeof token === "string") {
|
||||
res.status(401).json({ response: token });
|
||||
return null;
|
||||
}
|
||||
|
||||
const userId = token?.id;
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: userId,
|
||||
|
||||
Reference in New Issue
Block a user