diff options
| author | Dhravya Shah <[email protected]> | 2025-02-14 15:53:44 -0800 |
|---|---|---|
| committer | Dhravya Shah <[email protected]> | 2025-02-14 15:53:44 -0800 |
| commit | af1093d4aab6d5654364e7cd9df76fd766bbee03 (patch) | |
| tree | f128bdb8cac4b44ed41289dc9a158c33fe86c5ec | |
| parent | twitter import fix (diff) | |
| download | supermemory-af1093d4aab6d5654364e7cd9df76fd766bbee03.tar.xz supermemory-af1093d4aab6d5654364e7cd9df76fd766bbee03.zip | |
intuitive memory movement, avoid duplicates in home
| -rw-r--r-- | apps/backend/src/index.tsx | 4 | ||||
| -rw-r--r-- | apps/backend/src/routes/memories.ts | 32 | ||||
| -rw-r--r-- | apps/backend/src/routes/spaces.ts | 116 | ||||
| -rw-r--r-- | apps/web/app/components/memories/SharedCard.tsx | 12 | ||||
| -rw-r--r-- | apps/web/app/lib/hooks/use-spaces.tsx | 22 | ||||
| -rw-r--r-- | apps/web/app/routes/content.$contentid.tsx | 10 |
6 files changed, 183 insertions, 13 deletions
diff --git a/apps/backend/src/index.tsx b/apps/backend/src/index.tsx index 1d62b543..48790e90 100644 --- a/apps/backend/src/index.tsx +++ b/apps/backend/src/index.tsx @@ -47,6 +47,10 @@ export const app = new Hono<{ Variables: Variables; Bindings: Env }>() .use("/v1/*", auth) .use("/v1/*", (c, next) => { const user = c.get("user"); + + if (c.env.NODE_ENV === "development") { + return next(); + } // RATELIMITS const rateLimitConfig = { diff --git a/apps/backend/src/routes/memories.ts b/apps/backend/src/routes/memories.ts index efcc6fb7..aa35877b 100644 --- a/apps/backend/src/routes/memories.ts +++ b/apps/backend/src/routes/memories.ts @@ -8,7 +8,7 @@ import { spaceAccess, contentToSpace, } from "@supermemory/db/schema"; -import { and, database, desc, eq, or, sql } from "@supermemory/db"; +import { and, database, desc, eq, or, sql, isNull } from "@supermemory/db"; const memories = new Hono<{ Variables: Variables; Bindings: Env }>() .get( @@ -124,9 +124,20 @@ const memories = new Hono<{ Variables: Variables; Bindings: Env }>() const [items, [{ total }]] = await Promise.all([ db - .select() + .select({ + documents: documents, + }) .from(documents) - .where(eq(documents.userId, user.id)) + .leftJoin( + contentToSpace, + eq(documents.id, contentToSpace.contentId) + ) + .where( + and( + eq(documents.userId, user.id), + isNull(contentToSpace.contentId) + ) + ) .orderBy(desc(documents.createdAt)) .limit(count) .offset(start), @@ -135,13 +146,22 @@ const memories = new Hono<{ Variables: Variables; Bindings: Env }>() total: sql<number>`count(*)`.as("total"), }) .from(documents) - .where(eq(documents.userId, user.id)), + .leftJoin( + contentToSpace, + eq(documents.id, contentToSpace.contentId) + ) + .where( + and( + eq(documents.userId, user.id), + isNull(contentToSpace.contentId) + ) + ), ]); return c.json({ items: items.map((item) => ({ - ...item, - id: item.uuid, + ...item.documents, + id: item.documents.uuid, })), total, }); diff --git a/apps/backend/src/routes/spaces.ts b/apps/backend/src/routes/spaces.ts index 2ca2e461..bede366c 100644 --- a/apps/backend/src/routes/spaces.ts +++ b/apps/backend/src/routes/spaces.ts @@ -99,6 +99,7 @@ const spacesRoute = new Hono<{ Variables: Variables; Bindings: Env }>() const spaceId = c.req.param("spaceId"); const db = database(c.env.HYPERDRIVE.connectionString); + const space = await db .select() .from(spaces) @@ -178,6 +179,10 @@ const spacesRoute = new Hono<{ Variables: Variables; Bindings: Env }>() return c.json({ error: "Unauthorized" }, 401); } + if (body.spaceName.trim() === "<HOME>") { + return c.json({ error: "Cannot create space with name <HOME>" }, 400); + } + const db = database(c.env.HYPERDRIVE.connectionString); const uuid = randomId(); @@ -264,6 +269,99 @@ const spacesRoute = new Hono<{ Variables: Variables; Bindings: Env }>() } ) .post( + "/moveContent", + zValidator( + "json", + z.object({ + spaceId: z.string(), + documentId: z.string(), + }) + ), + async (c) => { + const body = c.req.valid("json"); + const user = c.get("user"); + const { spaceId, documentId } = body; + + if (!user) { + return c.json({ error: "Unauthorized" }, 401); + } + + const db = database(c.env.HYPERDRIVE.connectionString); + + try { + await db.transaction(async (tx) => { + // If moving to <HOME>, just remove all space connections + if (spaceId === "<HOME>") { + const doc = await tx + .select() + .from(documents) + .where(eq(documents.uuid, documentId)) + .limit(1); + + if (!doc[0]) { + return c.json({ error: "Document not found" }, 404); + } + + await tx + .delete(contentToSpace) + .where(eq(contentToSpace.contentId, doc[0].id)); + return; + } + + // Get space and document, verify space ownership + const results = ( + await tx + .select({ + spaceId: spaces.id, + documentId: documents.id, + ownerId: spaces.ownerId, + spaceName: spaces.name, + }) + .from(spaces) + .innerJoin( + documents, + and(eq(spaces.uuid, spaceId), eq(documents.uuid, documentId)) + ) + .limit(1) + )[0]; + + if (!results) { + return c.json({ error: "Space or document not found" }, 404); + } + + if (results.ownerId !== user.id) { + return c.json( + { error: "Not authorized to modify this space" }, + 403 + ); + } + + // Delete existing space relations for this document + await tx + .delete(contentToSpace) + .where(eq(contentToSpace.contentId, results.documentId)); + + // Add new space relation + await tx.insert(contentToSpace).values({ + contentId: results.documentId, + spaceId: results.spaceId, + }); + }); + + return c.json({ success: true, spaceId }); + } catch (e) { + console.error("Failed to move content to space:", e); + return c.json( + { + error: "Failed to move content to space", + details: e instanceof Error ? e.message : "Unknown error", + }, + 500 + ); + } + } + ) + .post( "/addContent", zValidator( "json", @@ -285,6 +383,24 @@ const spacesRoute = new Hono<{ Variables: Variables; Bindings: Env }>() try { await db.transaction(async (tx) => { + // If adding to <HOME>, just remove all space connections + if (spaceId === "<HOME>") { + const doc = await tx + .select() + .from(documents) + .where(eq(documents.uuid, documentId)) + .limit(1); + + if (!doc[0]) { + return c.json({ error: "Document not found" }, 404); + } + + await tx + .delete(contentToSpace) + .where(eq(contentToSpace.contentId, doc[0].id)); + return; + } + // Get space and document, verify space ownership const results = ( await tx diff --git a/apps/web/app/components/memories/SharedCard.tsx b/apps/web/app/components/memories/SharedCard.tsx index d80b848a..8e4c69b1 100644 --- a/apps/web/app/components/memories/SharedCard.tsx +++ b/apps/web/app/components/memories/SharedCard.tsx @@ -41,6 +41,7 @@ import { ExtraSpaceMetaData, fetchSpaces } from "~/lib/hooks/use-spaces"; import { useTextOverflow } from "~/lib/hooks/use-text-overflow"; import { Memory, WebsiteMetadata } from "~/lib/types/memory"; import { cn } from "~/lib/utils"; +import { useNavigate } from "@remix-run/react"; const { useTweet } = ReactTweet; @@ -616,6 +617,7 @@ export function FetchAndRenderContent({ content }: { content: string }) { function SharedCard({ data }: { data: Memory }) { const queryClient = useQueryClient(); + const navigate = useNavigate(); // Delete mutation const deleteMutation = useMutation({ @@ -657,7 +659,7 @@ function SharedCard({ data }: { data: Memory }) { // Move to space mutation const moveToSpaceMutation = useMutation({ mutationFn: async ({ spaceId, documentId }: { spaceId: string; documentId: string }) => { - const response = await fetch("/backend/v1/spaces/addContent", { + const response = await fetch("/backend/v1/spaces/moveContent", { method: "POST", headers: { "Content-Type": "application/json", @@ -668,15 +670,19 @@ function SharedCard({ data }: { data: Memory }) { if (!response.ok) { throw new Error("Failed to move memory"); } - return response.json(); + return response.json() as Promise<{ spaceId: string }>; }, onError: (err) => { toast.error("Failed to move memory to space"); }, - onSuccess: () => { + onSuccess: ({ spaceId }: { spaceId: string }) => { toast.success("Memory moved successfully"); queryClient.invalidateQueries({ queryKey: ["memories"] }); queryClient.invalidateQueries({ queryKey: ["spaces"] }); + if (spaceId === "<HOME>") { + return navigate("/"); + } + return navigate(`/space/${spaceId}`); }, }); diff --git a/apps/web/app/lib/hooks/use-spaces.tsx b/apps/web/app/lib/hooks/use-spaces.tsx index 14ffb56c..1654fd47 100644 --- a/apps/web/app/lib/hooks/use-spaces.tsx +++ b/apps/web/app/lib/hooks/use-spaces.tsx @@ -44,7 +44,27 @@ export async function fetchSpaces(): Promise<SpaceResponse> { throw new Error("Failed to fetch spaces"); } - return response.json(); + const resp = (await response.json()) as SpaceResponse; + + resp.spaces.push({ + id: 0, + uuid: "<HOME>", + name: "Home", + createdAt: new Date(), + updatedAt: new Date(), + ownerId: 0, + isPublic: false, + permissions: { + canRead: false, + canEdit: false, + isOwner: false, + isPublic: false, + }, + owner: null, + favorited: false, + }); + + return resp; } async function createSpace(data: { diff --git a/apps/web/app/routes/content.$contentid.tsx b/apps/web/app/routes/content.$contentid.tsx index ca81d039..da7e23d7 100644 --- a/apps/web/app/routes/content.$contentid.tsx +++ b/apps/web/app/routes/content.$contentid.tsx @@ -94,7 +94,7 @@ export default function Content() { // Move to space mutation const moveToSpaceMutation = useMutation({ mutationFn: async ({ spaceId, documentId }: { spaceId: string; documentId: string }) => { - const response = await fetch("/backend/v1/spaces/addContent", { + const response = await fetch("/backend/v1/spaces/moveContent", { method: "POST", headers: { "Content-Type": "application/json", @@ -105,12 +105,16 @@ export default function Content() { if (!response.ok) { throw new Error("Failed to move memory"); } - return response.json(); + return response.json() as Promise<{ spaceId: string }>; }, - onSuccess: () => { + onSuccess: ({ spaceId }: {spaceId: string}) => { toast.success("Memory moved successfully"); queryClient.invalidateQueries({ queryKey: ["memories"] }); queryClient.invalidateQueries({ queryKey: ["spaces"] }); + if (spaceId === "<HOME>") { + return navigate("/"); + } + return navigate(`/space/${spaceId}`); }, onError: () => { toast.error("Failed to move memory to space"); |