From c8a3c1ee02e917b2e553d403b7bf94cbc736f51d Mon Sep 17 00:00:00 2001
From: MohamedBassem <me@mbassem.com>
Date: Sat, 5 Oct 2024 14:42:22 +0000
Subject: feature(web): Allow users to export their links and notes

---
 apps/web/app/api/bookmarks/export/route.tsx | 66 +++++++++++++++++++++++++++++
 1 file changed, 66 insertions(+)
 create mode 100644 apps/web/app/api/bookmarks/export/route.tsx

(limited to 'apps/web/app/api/bookmarks/export/route.tsx')

diff --git a/apps/web/app/api/bookmarks/export/route.tsx b/apps/web/app/api/bookmarks/export/route.tsx
new file mode 100644
index 00000000..3b12b878
--- /dev/null
+++ b/apps/web/app/api/bookmarks/export/route.tsx
@@ -0,0 +1,66 @@
+import { api, createContextFromRequest } from "@/server/api/client";
+
+import type { ZBookmark } from "@hoarder/shared/types/bookmarks";
+import {
+  BookmarkTypes,
+  MAX_NUM_BOOKMARKS_PER_PAGE,
+} from "@hoarder/shared/types/bookmarks";
+
+function toExportFormat(bookmark: ZBookmark) {
+  return {
+    createdAt: bookmark.createdAt.toISOString(),
+    title:
+      bookmark.title ??
+      (bookmark.content.type === BookmarkTypes.LINK
+        ? bookmark.content.title
+        : null),
+    tags: bookmark.tags.map((t) => t.name),
+    type: bookmark.content.type,
+    url:
+      bookmark.content.type === BookmarkTypes.LINK
+        ? bookmark.content.url
+        : undefined,
+    text:
+      bookmark.content.type === BookmarkTypes.TEXT
+        ? bookmark.content.text
+        : undefined,
+    note: bookmark.note,
+  };
+}
+
+export const dynamic = "force-dynamic";
+export async function GET(request: Request) {
+  const ctx = await createContextFromRequest(request);
+  if (!ctx.user) {
+    return Response.json({ error: "Unauthorized" }, { status: 401 });
+  }
+  const req = {
+    limit: MAX_NUM_BOOKMARKS_PER_PAGE,
+    useCursorV2: true,
+  };
+
+  let resp = await api.bookmarks.getBookmarks(req);
+  let results = resp.bookmarks.map(toExportFormat);
+
+  while (resp.nextCursor) {
+    resp = await api.bookmarks.getBookmarks({
+      ...request,
+      cursor: resp.nextCursor,
+    });
+    results = [...results, ...resp.bookmarks.map(toExportFormat)];
+  }
+
+  return new Response(
+    JSON.stringify({
+      // Exclude asset types for now
+      bookmarks: results.filter((b) => b.type !== BookmarkTypes.ASSET),
+    }),
+    {
+      status: 200,
+      headers: {
+        "Content-type": "application/json",
+        "Content-disposition": `attachment; filename="hoarder-export-${new Date().toISOString()}.json"`,
+      },
+    },
+  );
+}
-- 
cgit v1.2.3-70-g09d2

