Skip to content

Commit

Permalink
feature: Dedup links on creation. Fixes #49
Browse files Browse the repository at this point in the history
  • Loading branch information
MohamedBassem committed May 6, 2024
1 parent 02ef4bf commit 32b5a02
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 106 deletions.
9 changes: 8 additions & 1 deletion apps/mobile/app/dashboard/add-link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,24 @@ import { Text, View } from "react-native";
import { useRouter } from "expo-router";
import { Button } from "@/components/ui/Button";
import { Input } from "@/components/ui/Input";
import { useToast } from "@/components/ui/Toast";
import { api } from "@/lib/trpc";

export default function AddNote() {
const [text, setText] = useState("");
const [error, setError] = useState<string | undefined>();
const { toast } = useToast();
const router = useRouter();
const invalidateAllBookmarks =
api.useUtils().bookmarks.getBookmarks.invalidate;

const { mutate } = api.bookmarks.createBookmark.useMutation({
onSuccess: () => {
onSuccess: (resp) => {
if (resp.alreadyExists) {
toast({
message: "Bookmark already exists",
});
}
invalidateAllBookmarks();
if (router.canGoBack()) {
router.replace("../");
Expand Down
12 changes: 10 additions & 2 deletions apps/mobile/app/sharing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ import type { ZBookmark } from "@hoarder/shared/types/bookmarks";
type Mode =
| { type: "idle" }
| { type: "success"; bookmarkId: string }
| { type: "alreadyExists"; bookmarkId: string }
| { type: "error" };

function SaveBookmark({ setMode }: { setMode: (mode: Mode) => void }) {
const onSaved = (d: ZBookmark) => {
const onSaved = (d: ZBookmark & { alreadyExists: boolean }) => {
invalidateAllBookmarks();
setMode({ type: "success", bookmarkId: d.id });
setMode({
type: d.alreadyExists ? "alreadyExists" : "success",
bookmarkId: d.id,
});
};

const { hasShareIntent, shareIntent, resetShareIntent } =
Expand Down Expand Up @@ -86,6 +90,10 @@ export default function Sharing() {
comp = <Text className="text-4xl text-foreground">Hoarded!</Text>;
break;
}
case "alreadyExists": {
comp = <Text className="text-4xl text-foreground">Already Hoarded!</Text>;
break;
}
case "error": {
comp = <Text className="text-4xl text-foreground">Error!</Text>;
break;
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/lib/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { api } from "./trpc";
export function useUploadAsset(
settings: Settings,
options: {
onSuccess?: (bookmark: ZBookmark) => void;
onSuccess?: (bookmark: ZBookmark & { alreadyExists: boolean }) => void;
onError?: (e: string) => void;
},
) {
Expand Down
20 changes: 19 additions & 1 deletion apps/web/components/dashboard/bookmarks/EditorCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { SubmitErrorHandler, SubmitHandler } from "react-hook-form";
import { useEffect, useImperativeHandle, useRef } from "react";
import Link from "next/link";
import { ActionButton } from "@/components/ui/action-button";
import { Form, FormControl, FormItem } from "@/components/ui/form";
import InfoTooltip from "@/components/ui/info-tooltip";
Expand All @@ -10,6 +11,7 @@ import { useClientConfig } from "@/lib/clientConfig";
import { useBookmarkLayoutSwitch } from "@/lib/userLocalSettings/bookmarksLayout";
import { cn } from "@/lib/utils";
import { zodResolver } from "@hookform/resolvers/zod";
import { ExternalLink } from "lucide-react";
import { useForm } from "react-hook-form";
import { z } from "zod";

Expand Down Expand Up @@ -50,7 +52,23 @@ export default function EditorCard({ className }: { className?: string }) {
useFocusOnKeyPress(inputRef);

const { mutate, isPending } = useCreateBookmarkWithPostHook({
onSuccess: () => {
onSuccess: (resp) => {
if (resp.alreadyExists) {
toast({
description: (
<div className="flex items-center gap-1">
Bookmark already exists.
<Link
className="flex underline-offset-4 hover:underline"
href={`/dashboard/preview/${resp.id}`}
>
Open <ExternalLink className="ml-1 size-4" />
</Link>
</div>
),
variant: "default",
});
}
form.reset();
},
onError: () => {
Expand Down
4 changes: 4 additions & 0 deletions packages/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ export const bookmarkLinks = sqliteTable("bookmarkLinks", {
crawlStatus: text("crawlStatus", {
enum: ["pending", "failure", "success"],
}).default("pending"),
}, (bl) => {
return {
urlIdx: index("bookmarkLinks_url_idx").on(bl.url),
};
});

export const bookmarkTexts = sqliteTable("bookmarkTexts", {
Expand Down
5 changes: 5 additions & 0 deletions packages/trpc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ export interface Context {
db: typeof db;
}

export interface AuthedContext {
user: User;
db: typeof db;
}

// Avoid exporting the entire t-object
// since it's not very descriptive.
// For instance, the use of a t variable
Expand Down
12 changes: 6 additions & 6 deletions packages/trpc/routers/bookmarks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,18 +116,18 @@ describe("Bookmark Routes", () => {

test<CustomTestContext>("update tags", async ({ apiCallers }) => {
const api = apiCallers[0].bookmarks;
let bookmark = await api.createBookmark({
const createdBookmark = await api.createBookmark({
url: "https://google.com",
type: "link",
});

await api.updateTags({
bookmarkId: bookmark.id,
bookmarkId: createdBookmark.id,
attach: [{ tagName: "tag1" }, { tagName: "tag2" }],
detach: [],
});

bookmark = await api.getBookmark({ bookmarkId: bookmark.id });
let bookmark = await api.getBookmark({ bookmarkId: createdBookmark.id });
expect(bookmark.tags.map((t) => t.name).sort()).toEqual(["tag1", "tag2"]);

const tag1Id = bookmark.tags.filter((t) => t.name == "tag1")[0].id;
Expand Down Expand Up @@ -157,17 +157,17 @@ describe("Bookmark Routes", () => {

test<CustomTestContext>("update bookmark text", async ({ apiCallers }) => {
const api = apiCallers[0].bookmarks;
let bookmark = await api.createBookmark({
const createdBookmark = await api.createBookmark({
text: "HELLO WORLD",
type: "text",
});

await api.updateBookmarkText({
bookmarkId: bookmark.id,
bookmarkId: createdBookmark.id,
text: "WORLD HELLO",
});

bookmark = await api.getBookmark({ bookmarkId: bookmark.id });
const bookmark = await api.getBookmark({ bookmarkId: createdBookmark.id });
assert(bookmark.content.type == "text");
expect(bookmark.content.text).toEqual("WORLD HELLO");
});
Expand Down
Loading

0 comments on commit 32b5a02

Please sign in to comment.