diff options
| -rw-r--r-- | packages/shared/queues.ts | 1 | ||||
| -rw-r--r-- | packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx | 25 | ||||
| -rw-r--r-- | packages/web/server/api/routers/bookmarks.ts | 39 | ||||
| -rw-r--r-- | packages/workers/crawler.ts | 14 | ||||
| -rw-r--r-- | packages/workers/openai.ts | 1 |
5 files changed, 76 insertions, 4 deletions
diff --git a/packages/shared/queues.ts b/packages/shared/queues.ts index 6a49d749..190aef85 100644 --- a/packages/shared/queues.ts +++ b/packages/shared/queues.ts @@ -9,7 +9,6 @@ export const queueConnectionDetails = { // Link Crawler export const zCrawlLinkRequestSchema = z.object({ bookmarkId: z.string(), - url: z.string().url(), }); export type ZCrawlLinkRequest = z.infer<typeof zCrawlLinkRequestSchema>; diff --git a/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx b/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx index 4496d820..4123dc36 100644 --- a/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx +++ b/packages/web/app/dashboard/bookmarks/components/BookmarkOptions.tsx @@ -11,7 +11,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { Archive, MoreHorizontal, Star, Trash2 } from "lucide-react"; +import { Archive, MoreHorizontal, RotateCw, Star, Trash2 } from "lucide-react"; export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) { const { toast } = useToast(); @@ -55,6 +55,25 @@ export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) { router.refresh(); }; + const crawlBookmark = async () => { + try { + await api.bookmarks.recrawlBookmark.mutate({ + bookmarkId: linkId, + }); + toast({ + description: "Re-fetch has been enqueued!", + }); + } catch (e) { + toast({ + variant: "destructive", + title: "Something went wrong", + description: "There was a problem with your request.", + }); + } + + router.refresh(); + }; + return ( <DropdownMenu> <DropdownMenuTrigger asChild> @@ -82,6 +101,10 @@ export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) { <Archive className="mr-2 size-4" /> <span>{bookmark.archived ? "Un-archive" : "Archive"}</span> </DropdownMenuItem> + <DropdownMenuItem onClick={crawlBookmark}> + <RotateCw className="mr-2 size-4" /> + <span>Refresh</span> + </DropdownMenuItem> <DropdownMenuItem className="text-destructive" onClick={unbookmarkLink}> <Trash2 className="mr-2 size-4" /> <span>Delete</span> diff --git a/packages/web/server/api/routers/bookmarks.ts b/packages/web/server/api/routers/bookmarks.ts index 953dab66..a77275d9 100644 --- a/packages/web/server/api/routers/bookmarks.ts +++ b/packages/web/server/api/routers/bookmarks.ts @@ -11,6 +11,8 @@ import { } from "@/lib/types/api/bookmarks"; import { prisma } from "@remember/db"; import { LinkCrawlerQueue } from "@remember/shared/queues"; +import { TRPCError, experimental_trpcMiddleware } from "@trpc/server"; +import { User } from "next-auth"; const defaultBookmarkFields = { id: true, @@ -33,6 +35,32 @@ const defaultBookmarkFields = { }, }; +const ensureBookmarkOwnership = experimental_trpcMiddleware<{ + ctx: { user: User }; + input: { bookmarkId: string }; +}>().create(async (opts) => { + const bookmark = await prisma.bookmark.findUnique({ + where: { id: opts.input.bookmarkId }, + select: { + userId: true, + }, + }); + if (!bookmark) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Bookmark not found", + }); + } + if (bookmark.userId != opts.ctx.user.id) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "User is not allowed to access resource", + }); + } + + return opts.next(); +}); + async function dummyPrismaReturnType() { const x = await prisma.bookmark.findFirstOrThrow({ select: defaultBookmarkFields, @@ -82,7 +110,6 @@ export const bookmarksAppRouter = router({ // Enqueue crawling request await LinkCrawlerQueue.add("crawl", { bookmarkId: bookmark.id, - url: url, }); return toZodSchema(bookmark); @@ -91,6 +118,7 @@ export const bookmarksAppRouter = router({ updateBookmark: authedProcedure .input(zUpdateBookmarksRequestSchema) .output(zBookmarkSchema) + .use(ensureBookmarkOwnership) .mutation(async ({ input, ctx }) => { const bookmark = await prisma.bookmark.update({ where: { @@ -108,6 +136,7 @@ export const bookmarksAppRouter = router({ deleteBookmark: authedProcedure .input(z.object({ bookmarkId: z.string() })) + .use(ensureBookmarkOwnership) .mutation(async ({ input, ctx }) => { await prisma.bookmark.delete({ where: { @@ -116,6 +145,14 @@ export const bookmarksAppRouter = router({ }, }); }), + recrawlBookmark: authedProcedure + .input(z.object({ bookmarkId: z.string() })) + .use(ensureBookmarkOwnership) + .mutation(async ({ input }) => { + await LinkCrawlerQueue.add("crawl", { + bookmarkId: input.bookmarkId, + }); + }), getBookmarks: authedProcedure .input(zGetBookmarksRequestSchema) .output(zGetBookmarksResponseSchema) diff --git a/packages/workers/crawler.ts b/packages/workers/crawler.ts index 45d2f530..4febc1ca 100644 --- a/packages/workers/crawler.ts +++ b/packages/workers/crawler.ts @@ -70,6 +70,17 @@ export class CrawlerWorker { } } +async function getBookmarkUrl(bookmarkId: string) { + const bookmark = await prisma.bookmarkedLink.findUnique({ + where: { id: bookmarkId }, + }); + + if (!bookmark) { + throw new Error("The bookmark either doesn't exist or not a link"); + } + return bookmark.url; +} + async function crawlPage(url: string) { if (!browser) { throw new Error("The browser must have been initalized by this point."); @@ -98,7 +109,8 @@ async function runCrawler(job: Job<ZCrawlLinkRequest, void>) { return; } - const { url, bookmarkId } = request.data; + const { bookmarkId } = request.data; + const url = await getBookmarkUrl(bookmarkId); logger.info( `[Crawler][${jobId}] Will crawl "${url}" for link with id "${bookmarkId}"`, diff --git a/packages/workers/openai.ts b/packages/workers/openai.ts index 1adedeba..7c45b2cb 100644 --- a/packages/workers/openai.ts +++ b/packages/workers/openai.ts @@ -140,6 +140,7 @@ async function createTags(tags: string[], userId: string) { async function connectTags(bookmarkId: string, tagIds: string[]) { // TODO: Prisma doesn't support createMany in Sqlite + // TODO: This could fail on refetch if the tags are already there await Promise.all( tagIds.map((tagId) => { return prisma.tagsOnBookmarks.create({ |
