From 02ef13d0eb0381f955c02677f7cc154f2af347ee Mon Sep 17 00:00:00 2001 From: celine Date: Mon, 8 Dec 2025 19:56:59 -0500 Subject: [PATCH] make leaflets_to_documents nullable --- actions/deleteLeaflet.ts | 44 +++++++++++-------- .../publications/moveLeafletToPublication.ts | 3 ++ actions/publications/saveLeafletDraft.ts | 26 +++++++++++ app/(home-pages)/home/HomeLayout.tsx | 6 +-- app/(home-pages)/home/page.tsx | 2 +- .../looseleafs/LooseleafsLayout.tsx | 6 ++- app/(home-pages)/looseleafs/page.tsx | 2 +- app/[leaflet_id]/actions/HomeButton.tsx | 2 +- app/[leaflet_id]/actions/PublishButton.tsx | 26 ++++++++--- app/[leaflet_id]/publish/page.tsx | 4 +- .../dashboard/PublishedPostsLists.tsx | 2 +- components/ActionBar/Publications.tsx | 4 +- components/PageSWRDataProvider.tsx | 12 +++-- .../getPublicationMetadataFromLeafletData.ts | 38 +++++++++++++--- supabase/database.types.ts | 8 ++-- 15 files changed, 133 insertions(+), 52 deletions(-) create mode 100644 actions/publications/saveLeafletDraft.ts diff --git a/actions/deleteLeaflet.ts b/actions/deleteLeaflet.ts index d6108def..a25acb45 100644 --- a/actions/deleteLeaflet.ts +++ b/actions/deleteLeaflet.ts @@ -23,11 +23,13 @@ export async function deleteLeaflet(permission_token: PermissionToken) { // Check publication and document ownership in one query let { data: tokenData } = await supabaseServerClient .from("permission_tokens") - .select(` + .select( + ` id, leaflets_in_publications(publication, publications!inner(identity_did)), leaflets_to_documents(document, documents!inner(uri)) - `) + `, + ) .eq("id", permission_token.id) .single(); @@ -36,28 +38,34 @@ export async function deleteLeaflet(permission_token: PermissionToken) { const leafletInPubs = tokenData.leaflets_in_publications || []; if (leafletInPubs.length > 0) { if (!identity) { - throw new Error("Unauthorized: You must be logged in to delete a leaflet in a publication"); + throw new Error( + "Unauthorized: You must be logged in to delete a leaflet in a publication", + ); } const isOwner = leafletInPubs.some( - (pub: any) => pub.publications.identity_did === identity.atp_did + (pub: any) => pub.publications.identity_did === identity.atp_did, ); if (!isOwner) { - throw new Error("Unauthorized: You must own the publication to delete this leaflet"); + throw new Error( + "Unauthorized: You must own the publication to delete this leaflet", + ); } } // Check if there's a standalone published document - const leafletDocs = tokenData.leaflets_to_documents || []; - if (leafletDocs.length > 0) { - if (!identity) { - throw new Error("Unauthorized: You must be logged in to delete a published leaflet"); + const leafletDoc = tokenData.leaflets_to_documents; + if (leafletDoc && leafletDoc.document) { + if (!identity || !identity.atp_did) { + throw new Error( + "Unauthorized: You must be logged in to delete a published leaflet", + ); } - for (let leafletDoc of leafletDocs) { - const docUri = leafletDoc.documents?.uri; - // Extract the DID from the document URI (format: at://did:plc:xxx/...) - if (docUri && !docUri.includes(identity.atp_did)) { - throw new Error("Unauthorized: You must own the published document to delete this leaflet"); - } + const docUri = leafletDoc.documents?.uri; + // Extract the DID from the document URI (format: at://did:plc:xxx/...) + if (docUri && !docUri.includes(identity.atp_did)) { + throw new Error( + "Unauthorized: You must own the published document to delete this leaflet", + ); } } } @@ -73,9 +81,9 @@ export async function deleteLeaflet(permission_token: PermissionToken) { .where(eq(permission_tokens.id, permission_token.id)); if (!token?.permission_token_rights?.write) return; - await tx - .delete(entities) - .where(eq(entities.set, token.permission_token_rights.entity_set)); + const entitySet = token.permission_token_rights.entity_set; + if (!entitySet) return; + await tx.delete(entities).where(eq(entities.set, entitySet)); await tx .delete(permission_tokens) .where(eq(permission_tokens.id, permission_token.id)); diff --git a/actions/publications/moveLeafletToPublication.ts b/actions/publications/moveLeafletToPublication.ts index a3ee8c62..160ddc4a 100644 --- a/actions/publications/moveLeafletToPublication.ts +++ b/actions/publications/moveLeafletToPublication.ts @@ -11,6 +11,8 @@ export async function moveLeafletToPublication( ) { let identity = await getIdentityData(); if (!identity || !identity.atp_did) return null; + + // Verify publication ownership let { data: publication } = await supabaseServerClient .from("publications") .select("*") @@ -18,6 +20,7 @@ export async function moveLeafletToPublication( .single(); if (publication?.identity_did !== identity.atp_did) return; + // Save as a publication draft await supabaseServerClient.from("leaflets_in_publications").insert({ publication: publication_uri, leaflet: leaflet_id, diff --git a/actions/publications/saveLeafletDraft.ts b/actions/publications/saveLeafletDraft.ts new file mode 100644 index 00000000..c6650672 --- /dev/null +++ b/actions/publications/saveLeafletDraft.ts @@ -0,0 +1,26 @@ +"use server"; + +import { getIdentityData } from "actions/getIdentityData"; +import { supabaseServerClient } from "supabase/serverClient"; + +export async function saveLeafletDraft( + leaflet_id: string, + metadata: { title: string; description: string }, + entitiesToDelete: string[], +) { + let identity = await getIdentityData(); + if (!identity || !identity.atp_did) return null; + + // Save as a looseleaf draft in leaflets_to_documents with null document + await supabaseServerClient.from("leaflets_to_documents").upsert({ + leaflet: leaflet_id, + document: null, + title: metadata.title, + description: metadata.description, + }); + + await supabaseServerClient + .from("entities") + .delete() + .in("id", entitiesToDelete); +} diff --git a/app/(home-pages)/home/HomeLayout.tsx b/app/(home-pages)/home/HomeLayout.tsx index 568a6e7e..744b4d5d 100644 --- a/app/(home-pages)/home/HomeLayout.tsx +++ b/app/(home-pages)/home/HomeLayout.tsx @@ -136,7 +136,7 @@ export function HomeLeafletList(props: { (acc, tok) => { let title = tok.permission_tokens.leaflets_in_publications[0]?.title || - tok.permission_tokens.leaflets_to_documents[0]?.title; + tok.permission_tokens.leaflets_to_documents?.title; if (title) acc[tok.permission_tokens.root_entity] = title; return acc; }, @@ -233,7 +233,7 @@ export function LeafletList(props: { value={{ ...leaflet, leaflets_in_publications: leaflet.leaflets_in_publications || [], - leaflets_to_documents: leaflet.leaflets_to_documents || [], + leaflets_to_documents: leaflet.leaflets_to_documents || null, blocked_by_admin: null, custom_domain_routes: [], }} @@ -292,7 +292,7 @@ function useSearchedLeaflets( ({ token: leaflet, archived: archived }) => { let published = !!leaflet.leaflets_in_publications?.find((l) => l.doc) || - !!leaflet.leaflets_to_documents?.find((l) => l.document); + !!leaflet.leaflets_to_documents?.document; let drafts = !!leaflet.leaflets_in_publications?.length && !published; let docs = !leaflet.leaflets_in_publications?.length && !archived; // If no filters are active, show all diff --git a/app/(home-pages)/home/page.tsx b/app/(home-pages)/home/page.tsx index 5408ab37..9b783167 100644 --- a/app/(home-pages)/home/page.tsx +++ b/app/(home-pages)/home/page.tsx @@ -30,7 +30,7 @@ export default async function Home() { (acc, tok) => { let title = tok.permission_tokens.leaflets_in_publications[0]?.title || - tok.permission_tokens.leaflets_to_documents[0]?.title; + tok.permission_tokens.leaflets_to_documents?.title; if (title) acc[tok.permission_tokens.root_entity] = title; return acc; }, diff --git a/app/(home-pages)/looseleafs/LooseleafsLayout.tsx b/app/(home-pages)/looseleafs/LooseleafsLayout.tsx index fd1ba95b..c96140ac 100644 --- a/app/(home-pages)/looseleafs/LooseleafsLayout.tsx +++ b/app/(home-pages)/looseleafs/LooseleafsLayout.tsx @@ -111,7 +111,7 @@ export const LooseleafList = (props: { (acc, tok) => { let title = tok.permission_tokens.leaflets_in_publications[0]?.title || - tok.permission_tokens.leaflets_to_documents[0]?.title; + tok.permission_tokens.leaflets_to_documents?.title; if (title) acc[tok.permission_tokens.root_entity] = title; return acc; }, @@ -127,7 +127,9 @@ export const LooseleafList = (props: { let leaflets: Leaflet[] = identity ? identity.permission_token_on_homepage .filter( - (ptoh) => ptoh.permission_tokens.leaflets_to_documents.length > 0, + (ptoh) => + ptoh.permission_tokens.leaflets_to_documents && + ptoh.permission_tokens.leaflets_to_documents.document, ) .map((ptoh) => ({ added_at: ptoh.created_at, diff --git a/app/(home-pages)/looseleafs/page.tsx b/app/(home-pages)/looseleafs/page.tsx index 9ae9f7b3..1b2fc02c 100644 --- a/app/(home-pages)/looseleafs/page.tsx +++ b/app/(home-pages)/looseleafs/page.tsx @@ -34,7 +34,7 @@ export default async function Home() { (acc, tok) => { let title = tok.permission_tokens.leaflets_in_publications[0]?.title || - tok.permission_tokens.leaflets_to_documents[0]?.title; + tok.permission_tokens.leaflets_to_documents?.title; if (title) acc[tok.permission_tokens.root_entity] = title; return acc; }, diff --git a/app/[leaflet_id]/actions/HomeButton.tsx b/app/[leaflet_id]/actions/HomeButton.tsx index 54d54753..14cae86f 100644 --- a/app/[leaflet_id]/actions/HomeButton.tsx +++ b/app/[leaflet_id]/actions/HomeButton.tsx @@ -53,7 +53,7 @@ const AddToHomeButton = (props: {}) => { archived: null, permission_tokens: { ...permission_token, - leaflets_to_documents: [], + leaflets_to_documents: null, leaflets_in_publications: [], }, }); diff --git a/app/[leaflet_id]/actions/PublishButton.tsx b/app/[leaflet_id]/actions/PublishButton.tsx index 4098d1ff..2cde46a8 100644 --- a/app/[leaflet_id]/actions/PublishButton.tsx +++ b/app/[leaflet_id]/actions/PublishButton.tsx @@ -37,6 +37,7 @@ import * as base64 from "base64-js"; import { YJSFragmentToString } from "components/Blocks/TextBlock/RenderYJSFragment"; import { BlueskyLogin } from "app/login/LoginForm"; import { moveLeafletToPublication } from "actions/publications/moveLeafletToPublication"; +import { saveLeafletDraft } from "actions/publications/saveLeafletDraft"; import { AddTiny } from "components/Icons/AddTiny"; export const PublishButton = (props: { entityID: string }) => { @@ -176,7 +177,7 @@ const PublishToPublicationButton = (props: { entityID: string }) => {
- {selectedPub !== "looseleaf" && selectedPub && ( + {selectedPub && selectedPub !== "create" && ( { let { identity } = useIdentityData(); let hasLooseleafs = identity?.permission_token_on_homepage.find( - (f) => f.permission_tokens.leaflets_to_documents[0], + (f) => + f.permission_tokens.leaflets_to_documents && + f.permission_tokens.leaflets_to_documents.document, ); console.log(hasLooseleafs); diff --git a/components/PageSWRDataProvider.tsx b/components/PageSWRDataProvider.tsx index ab866443..3d5a4e46 100644 --- a/components/PageSWRDataProvider.tsx +++ b/components/PageSWRDataProvider.tsx @@ -90,16 +90,20 @@ export function useLeafletPublicationStatus() { const publishedInPublication = data.leaflets_in_publications?.find( (l) => l.doc, ); - const publishedStandalone = data.leaflets_to_documents?.find( - (l) => !!l.documents, - ); + const publishedStandalone = + data.leaflets_to_documents && data.leaflets_to_documents.documents + ? data.leaflets_to_documents + : null; const documentUri = publishedInPublication?.documents?.uri ?? publishedStandalone?.document; // Compute the full post URL for sharing let postShareLink: string | undefined; - if (publishedInPublication?.publications && publishedInPublication.documents) { + if ( + publishedInPublication?.publications && + publishedInPublication.documents + ) { // Published in a publication - use publication URL + document rkey const docUri = new AtUri(publishedInPublication.documents.uri); postShareLink = `${getPublicationURL(publishedInPublication.publications)}/${docUri.rkey}`; diff --git a/src/utils/getPublicationMetadataFromLeafletData.ts b/src/utils/getPublicationMetadataFromLeafletData.ts index 32483bd4..2de33b7a 100644 --- a/src/utils/getPublicationMetadataFromLeafletData.ts +++ b/src/utils/getPublicationMetadataFromLeafletData.ts @@ -32,19 +32,43 @@ export function getPublicationMetadataFromLeafletData( (p) => p.leaflets_in_publications?.length, )?.leaflets_in_publications?.[0]; - // If not found, check for standalone documents - let standaloneDoc = - data?.leaflets_to_documents?.[0] || - data?.permission_token_rights[0].entity_sets?.permission_tokens.find( - (p) => p.leaflets_to_documents?.length, - )?.leaflets_to_documents?.[0]; - if (!pubData && standaloneDoc) { + // If not found, check for standalone documents (looseleafs) + let standaloneDoc = data?.leaflets_to_documents; + + // Only use standaloneDoc if it exists and has meaningful data + // (either published with a document, or saved as draft with a title) + if ( + !pubData && + standaloneDoc && + (standaloneDoc.document || standaloneDoc.title) + ) { // Transform standalone document data to match the expected format pubData = { ...standaloneDoc, publications: null, // No publication for standalone docs doc: standaloneDoc.document, + leaflet: data.id, }; } + + // Also check nested permission tokens for looseleafs + if (!pubData) { + let nestedStandaloneDoc = + data?.permission_token_rights[0].entity_sets?.permission_tokens?.find( + (p) => + p.leaflets_to_documents && + (p.leaflets_to_documents.document || p.leaflets_to_documents.title), + )?.leaflets_to_documents; + + if (nestedStandaloneDoc) { + pubData = { + ...nestedStandaloneDoc, + publications: null, + doc: nestedStandaloneDoc.document, + leaflet: data.id, + }; + } + } + return pubData; } diff --git a/supabase/database.types.ts b/supabase/database.types.ts index 43d56b03..ad8d757e 100644 --- a/supabase/database.types.ts +++ b/supabase/database.types.ts @@ -631,21 +631,21 @@ export type Database = { Row: { created_at: string description: string - document: string + document: string | null leaflet: string title: string } Insert: { created_at?: string description?: string - document: string + document?: string | null leaflet: string title?: string } Update: { created_at?: string description?: string - document?: string + document?: string | null leaflet?: string title?: string } @@ -660,7 +660,7 @@ export type Database = { { foreignKeyName: "leaflets_to_documents_leaflet_fkey" columns: ["leaflet"] - isOneToOne: false + isOneToOne: true referencedRelation: "permission_tokens" referencedColumns: ["id"] }, -- 2.51.2