···1import { NextRequest } from "next/server";
2import { IdResolver } from "@atproto/identity";
3-import { AtUri } from "@atproto/syntax";
4import { supabaseServerClient } from "supabase/serverClient";
5import sharp from "sharp";
6import { redirect } from "next/navigation";
7import { normalizePublicationRecord } from "src/utils/normalizeRecords";
089let idResolver = new IdResolver();
10···18 const params = await props.params;
19 try {
20 let did = decodeURIComponent(params.did);
21- let uri;
22- if (/^(?!\.$|\.\.S)[A-Za-z0-9._:~-]{1,512}$/.test(params.publication)) {
23- uri = AtUri.make(
24- did,
25- "pub.leaflet.publication",
26- params.publication,
27- ).toString();
28- }
29- let { data: publication } = await supabaseServerClient
30 .from("publications")
31 .select(
32 `*,
···35 `,
36 )
37 .eq("identity_did", did)
38- .or(`name.eq."${params.publication}", uri.eq."${uri}"`)
39- .single();
004041 const record = normalizePublicationRecord(publication?.record);
42 if (!record?.icon) return redirect("/icon.png");
···1import { NextRequest } from "next/server";
2import { IdResolver } from "@atproto/identity";
03import { supabaseServerClient } from "supabase/serverClient";
4import sharp from "sharp";
5import { redirect } from "next/navigation";
6import { normalizePublicationRecord } from "src/utils/normalizeRecords";
7+import { publicationNameOrUriFilter } from "src/utils/uriHelpers";
89let idResolver = new IdResolver();
10···18 const params = await props.params;
19 try {
20 let did = decodeURIComponent(params.did);
21+ let publication_name = decodeURIComponent(params.publication);
22+ let { data: publications } = await supabaseServerClient
000000023 .from("publications")
24 .select(
25 `*,
···28 `,
29 )
30 .eq("identity_did", did)
31+ .or(publicationNameOrUriFilter(did, publication_name))
32+ .order("uri", { ascending: false })
33+ .limit(1);
34+ let publication = publications?.[0];
3536 const record = normalizePublicationRecord(publication?.record);
37 if (!record?.icon) return redirect("/icon.png");
+6-12
app/lish/[did]/[publication]/layout.tsx
···1import { supabaseServerClient } from "supabase/serverClient";
2import { Metadata } from "next";
3-import { AtUri } from "@atproto/syntax";
4import { normalizePublicationRecord } from "src/utils/normalizeRecords";
056export default async function PublicationLayout(props: {
7 children: React.ReactNode;
···19 let did = decodeURIComponent(params.did);
20 if (!params.did || !params.publication) return { title: "Publication 404" };
2122- let uri;
23 let publication_name = decodeURIComponent(params.publication);
24- if (/^(?!\.$|\.\.S)[A-Za-z0-9._:~-]{1,512}$/.test(publication_name)) {
25- uri = AtUri.make(
26- did,
27- "pub.leaflet.publication",
28- publication_name,
29- ).toString();
30- }
31- let { data: publication } = await supabaseServerClient
32 .from("publications")
33 .select(
34 `*,
···37 `,
38 )
39 .eq("identity_did", did)
40- .or(`name.eq."${publication_name}", uri.eq."${uri}"`)
41- .single();
0042 if (!publication) return { title: "Publication 404" };
4344 const pubRecord = normalizePublicationRecord(publication?.record);
···1import { supabaseServerClient } from "supabase/serverClient";
2import { Metadata } from "next";
03import { normalizePublicationRecord } from "src/utils/normalizeRecords";
4+import { publicationNameOrUriFilter } from "src/utils/uriHelpers";
56export default async function PublicationLayout(props: {
7 children: React.ReactNode;
···19 let did = decodeURIComponent(params.did);
20 if (!params.did || !params.publication) return { title: "Publication 404" };
21022 let publication_name = decodeURIComponent(params.publication);
23+ let { data: publications } = await supabaseServerClient
000000024 .from("publications")
25 .select(
26 `*,
···29 `,
30 )
31 .eq("identity_did", did)
32+ .or(publicationNameOrUriFilter(did, publication_name))
33+ .order("uri", { ascending: false })
34+ .limit(1);
35+ let publication = publications?.[0];
36 if (!publication) return { title: "Publication 404" };
3738 const pubRecord = normalizePublicationRecord(publication?.record);
+6-11
app/lish/[did]/[publication]/page.tsx
···2import { AtUri } from "@atproto/syntax";
3import { getPublicationURL } from "app/lish/createPub/getPublicationURL";
4import { BskyAgent } from "@atproto/api";
05import { SubscribeWithBluesky } from "app/lish/Subscribe";
6import React from "react";
7import {
···27 let did = decodeURIComponent(params.did);
28 if (!did) return <PubNotFound />;
29 let agent = new BskyAgent({ service: "https://public.api.bsky.app" });
30- let uri;
31 let publication_name = decodeURIComponent(params.publication);
32- if (/^(?!\.$|\.\.S)[A-Za-z0-9._:~-]{1,512}$/.test(publication_name)) {
33- uri = AtUri.make(
34- did,
35- "pub.leaflet.publication",
36- publication_name,
37- ).toString();
38- }
39- let [{ data: publication }, { data: profile }] = await Promise.all([
40 supabaseServerClient
41 .from("publications")
42 .select(
···50 `,
51 )
52 .eq("identity_did", did)
53- .or(`name.eq."${publication_name}", uri.eq."${uri}"`)
54- .single(),
055 agent.getProfile({ actor: did }),
56 ]);
05758 const record = normalizePublicationRecord(publication?.record);
59
···2import { AtUri } from "@atproto/syntax";
3import { getPublicationURL } from "app/lish/createPub/getPublicationURL";
4import { BskyAgent } from "@atproto/api";
5+import { publicationNameOrUriFilter } from "src/utils/uriHelpers";
6import { SubscribeWithBluesky } from "app/lish/Subscribe";
7import React from "react";
8import {
···28 let did = decodeURIComponent(params.did);
29 if (!did) return <PubNotFound />;
30 let agent = new BskyAgent({ service: "https://public.api.bsky.app" });
031 let publication_name = decodeURIComponent(params.publication);
32+ let [{ data: publications }, { data: profile }] = await Promise.all([
000000033 supabaseServerClient
34 .from("publications")
35 .select(
···43 `,
44 )
45 .eq("identity_did", did)
46+ .or(publicationNameOrUriFilter(did, publication_name))
47+ .order("uri", { ascending: false })
48+ .limit(1),
49 agent.getProfile({ actor: did }),
50 ]);
51+ let publication = publications?.[0];
5253 const record = normalizePublicationRecord(publication?.record);
54
+46-21
app/lish/createPub/createPublication.ts
···1"use server";
2import { TID } from "@atproto/common";
3-import { AtpBaseClient, PubLeafletPublication } from "lexicons/api";
00004import {
5 restoreOAuthSession,
6 OAuthSessionError,
7} from "src/atproto-oauth";
8import { getIdentityData } from "actions/getIdentityData";
9import { supabaseServerClient } from "supabase/serverClient";
10-import { Un$Typed } from "@atproto/api";
11import { Json } from "supabase/database.types";
12import { Vercel } from "@vercel/sdk";
13import { isProductionDomain } from "src/utils/isProductionDeployment";
14import { string } from "zod";
01516const VERCEL_TOKEN = process.env.VERCEL_TOKEN;
17const vercel = new Vercel({
···64 let agent = new AtpBaseClient(
65 credentialSession.fetchHandler.bind(credentialSession),
66 );
67- let record: Un$Typed<PubLeafletPublication.Record> = {
68- name,
69- base_path: domain,
70- preferences,
71- };
7273- if (description) {
74- record.description = description;
75- }
00007677 // Upload the icon if provided
78 if (iconFile && iconFile.size > 0) {
···81 new Uint8Array(buffer),
82 { encoding: iconFile.type },
83 );
008485- if (uploadResult.data.blob) {
86- record.icon = uploadResult.data.blob;
87- }
0000000000000000000088 }
8990- let result = await agent.pub.leaflet.publication.create(
91- { repo: credentialSession.did!, rkey: TID.nextStr(), validate: false },
0092 record,
93- );
09495 //optimistically write to our db!
96 let { data: publication } = await supabaseServerClient
···98 .upsert({
99 uri: result.uri,
100 identity_did: credentialSession.did!,
101- name: record.name,
102- record: {
103- ...record,
104- $type: "pub.leaflet.publication",
105- } as unknown as Json,
106 })
107 .select()
108 .single();
···1"use server";
2import { TID } from "@atproto/common";
3+import {
4+ AtpBaseClient,
5+ PubLeafletPublication,
6+ SiteStandardPublication,
7+} from "lexicons/api";
8import {
9 restoreOAuthSession,
10 OAuthSessionError,
11} from "src/atproto-oauth";
12import { getIdentityData } from "actions/getIdentityData";
13import { supabaseServerClient } from "supabase/serverClient";
014import { Json } from "supabase/database.types";
15import { Vercel } from "@vercel/sdk";
16import { isProductionDomain } from "src/utils/isProductionDeployment";
17import { string } from "zod";
18+import { getPublicationType } from "src/utils/collectionHelpers";
1920const VERCEL_TOKEN = process.env.VERCEL_TOKEN;
21const vercel = new Vercel({
···68 let agent = new AtpBaseClient(
69 credentialSession.fetchHandler.bind(credentialSession),
70 );
000007172+ // Use site.standard.publication for new publications
73+ const publicationType = getPublicationType();
74+ const url = `https://${domain}`;
75+76+ // Build record based on publication type
77+ let record: SiteStandardPublication.Record | PubLeafletPublication.Record;
78+ let iconBlob: Awaited<ReturnType<typeof agent.com.atproto.repo.uploadBlob>>["data"]["blob"] | undefined;
7980 // Upload the icon if provided
81 if (iconFile && iconFile.size > 0) {
···84 new Uint8Array(buffer),
85 { encoding: iconFile.type },
86 );
87+ iconBlob = uploadResult.data.blob;
88+ }
8990+ if (publicationType === "site.standard.publication") {
91+ record = {
92+ $type: "site.standard.publication",
93+ name,
94+ url,
95+ ...(description && { description }),
96+ ...(iconBlob && { icon: iconBlob }),
97+ preferences: {
98+ showInDiscover: preferences.showInDiscover,
99+ showComments: preferences.showComments,
100+ showMentions: preferences.showMentions,
101+ showPrevNext: preferences.showPrevNext,
102+ },
103+ } satisfies SiteStandardPublication.Record;
104+ } else {
105+ record = {
106+ $type: "pub.leaflet.publication",
107+ name,
108+ base_path: domain,
109+ ...(description && { description }),
110+ ...(iconBlob && { icon: iconBlob }),
111+ preferences,
112+ } satisfies PubLeafletPublication.Record;
113 }
114115+ let { data: result } = await agent.com.atproto.repo.putRecord({
116+ repo: credentialSession.did!,
117+ rkey: TID.nextStr(),
118+ collection: publicationType,
119 record,
120+ validate: false,
121+ });
122123 //optimistically write to our db!
124 let { data: publication } = await supabaseServerClient
···126 .upsert({
127 uri: result.uri,
128 identity_did: credentialSession.did!,
129+ name,
130+ record: record as unknown as Json,
000131 })
132 .select()
133 .single();
+20-13
app/lish/createPub/updatePublication.ts
···15 normalizePublicationRecord,
16 type NormalizedPublication,
17} from "src/utils/normalizeRecords";
01819type UpdatePublicationResult =
20 | { success: true; publication: any }
···62 return { success: false };
63 }
64 let aturi = new AtUri(existingPub.uri);
006566- let record: PubLeafletPublication.Record = {
67- $type: "pub.leaflet.publication",
68 ...(existingPub.record as object),
69 name,
70- };
71 if (preferences) {
72 record.preferences = preferences;
73 }
···93 repo: credentialSession.did!,
94 rkey: aturi.rkey,
95 record,
96- collection: record.$type,
97 validate: false,
98 });
99···146 return { success: false };
147 }
148 let aturi = new AtUri(existingPub.uri);
00149150- // Normalize the existing record to read its properties, then build a new pub.leaflet record
151 const normalizedPub = normalizePublicationRecord(existingPub.record);
152 // Extract base_path from url if it exists (url format is https://domain, base_path is just domain)
153 const existingBasePath = normalizedPub?.url
154 ? normalizedPub.url.replace(/^https?:\/\//, "")
155 : undefined;
156157- let record: PubLeafletPublication.Record = {
158- $type: "pub.leaflet.publication",
159 name: normalizedPub?.name || "",
160 description: normalizedPub?.description,
161 icon: normalizedPub?.icon,
···170 }
171 : undefined,
172 base_path,
173- };
174175 let result = await agent.com.atproto.repo.putRecord({
176 repo: credentialSession.did!,
177 rkey: aturi.rkey,
178 record,
179- collection: record.$type,
180 validate: false,
181 });
182···242 return { success: false };
243 }
244 let aturi = new AtUri(existingPub.uri);
00245246 // Normalize the existing record to read its properties
247 const normalizedPub = normalizePublicationRecord(existingPub.record);
···250 ? normalizedPub.url.replace(/^https?:\/\//, "")
251 : undefined;
252253- let record: PubLeafletPublication.Record = {
254- $type: "pub.leaflet.publication",
255 name: normalizedPub?.name || "",
256 description: normalizedPub?.description,
257 icon: normalizedPub?.icon,
···301 ...theme.accentText,
302 },
303 },
304- };
305306 let result = await agent.com.atproto.repo.putRecord({
307 repo: credentialSession.did!,
308 rkey: aturi.rkey,
309 record,
310- collection: record.$type,
311 validate: false,
312 });
313
···15 normalizePublicationRecord,
16 type NormalizedPublication,
17} from "src/utils/normalizeRecords";
18+import { getPublicationType } from "src/utils/collectionHelpers";
1920type UpdatePublicationResult =
21 | { success: true; publication: any }
···63 return { success: false };
64 }
65 let aturi = new AtUri(existingPub.uri);
66+ // Preserve existing schema when updating
67+ const publicationType = getPublicationType(aturi.collection);
6869+ let record = {
70+ $type: publicationType,
71 ...(existingPub.record as object),
72 name,
73+ } as PubLeafletPublication.Record;
74 if (preferences) {
75 record.preferences = preferences;
76 }
···96 repo: credentialSession.did!,
97 rkey: aturi.rkey,
98 record,
99+ collection: publicationType,
100 validate: false,
101 });
102···149 return { success: false };
150 }
151 let aturi = new AtUri(existingPub.uri);
152+ // Preserve existing schema when updating
153+ const publicationType = getPublicationType(aturi.collection);
154155+ // Normalize the existing record to read its properties
156 const normalizedPub = normalizePublicationRecord(existingPub.record);
157 // Extract base_path from url if it exists (url format is https://domain, base_path is just domain)
158 const existingBasePath = normalizedPub?.url
159 ? normalizedPub.url.replace(/^https?:\/\//, "")
160 : undefined;
161162+ let record = {
163+ $type: publicationType,
164 name: normalizedPub?.name || "",
165 description: normalizedPub?.description,
166 icon: normalizedPub?.icon,
···175 }
176 : undefined,
177 base_path,
178+ } as PubLeafletPublication.Record;
179180 let result = await agent.com.atproto.repo.putRecord({
181 repo: credentialSession.did!,
182 rkey: aturi.rkey,
183 record,
184+ collection: publicationType,
185 validate: false,
186 });
187···247 return { success: false };
248 }
249 let aturi = new AtUri(existingPub.uri);
250+ // Preserve existing schema when updating
251+ const publicationType = getPublicationType(aturi.collection);
252253 // Normalize the existing record to read its properties
254 const normalizedPub = normalizePublicationRecord(existingPub.record);
···257 ? normalizedPub.url.replace(/^https?:\/\//, "")
258 : undefined;
259260+ let record = {
261+ $type: publicationType,
262 name: normalizedPub?.name || "",
263 description: normalizedPub?.description,
264 icon: normalizedPub?.icon,
···308 ...theme.accentText,
309 },
310 },
311+ } as PubLeafletPublication.Record;
312313 let result = await agent.com.atproto.repo.putRecord({
314 repo: credentialSession.did!,
315 rkey: aturi.rkey,
316 record,
317+ collection: publicationType,
318 validate: false,
319 });
320
+6-2
app/lish/uri/[uri]/route.ts
···5 normalizePublicationRecord,
6 type NormalizedPublication,
7} from "src/utils/normalizeRecords";
000089/**
10 * Redirect route for AT URIs (publications and documents)
···19 const atUriString = decodeURIComponent(uriParam);
20 const uri = new AtUri(atUriString);
2122- if (uri.collection === "pub.leaflet.publication") {
23 // Get the publication record to retrieve base_path
24 const { data: publication } = await supabaseServerClient
25 .from("publications")
···4041 // Redirect to the publication's hosted domain (temporary redirect since url can change)
42 return NextResponse.redirect(normalizedPub.url, 307);
43- } else if (uri.collection === "pub.leaflet.document") {
44 // Document link - need to find the publication it belongs to
45 const { data: docInPub } = await supabaseServerClient
46 .from("documents_in_publications")
···5 normalizePublicationRecord,
6 type NormalizedPublication,
7} from "src/utils/normalizeRecords";
8+import {
9+ isDocumentCollection,
10+ isPublicationCollection,
11+} from "src/utils/collectionHelpers";
1213/**
14 * Redirect route for AT URIs (publications and documents)
···23 const atUriString = decodeURIComponent(uriParam);
24 const uri = new AtUri(atUriString);
2526+ if (isPublicationCollection(uri.collection)) {
27 // Get the publication record to retrieve base_path
28 const { data: publication } = await supabaseServerClient
29 .from("publications")
···4445 // Redirect to the publication's hosted domain (temporary redirect since url can change)
46 return NextResponse.redirect(normalizedPub.url, 307);
47+ } else if (isDocumentCollection(uri.collection)) {
48 // Document link - need to find the publication it belongs to
49 const { data: docInPub } = await supabaseServerClient
50 .from("documents_in_publications")
+6-5
app/p/[didOrHandle]/[rkey]/opengraph-image.ts
···1import { getMicroLinkOgImage } from "src/utils/getMicroLinkOgImage";
2import { supabaseServerClient } from "supabase/serverClient";
3-import { AtUri } from "@atproto/syntax";
4-import { ids } from "lexicons/api/lexicons";
5import { jsonToLex } from "@atproto/lexicon";
6import { idResolver } from "app/(home-pages)/reader/idResolver";
7import { fetchAtprotoBlob } from "app/api/atproto_images/route";
8import { normalizeDocumentRecord } from "src/utils/normalizeRecords";
0910export const revalidate = 60;
11···2829 if (did) {
30 // Try to get the document's cover image
31- let { data: document } = await supabaseServerClient
32 .from("documents")
33 .select("data")
34- .eq("uri", AtUri.make(did, ids.PubLeafletDocument, params.rkey).toString())
35- .single();
003637 if (document) {
38 const docRecord = normalizeDocumentRecord(jsonToLex(document.data));
···1import { getMicroLinkOgImage } from "src/utils/getMicroLinkOgImage";
2import { supabaseServerClient } from "supabase/serverClient";
003import { jsonToLex } from "@atproto/lexicon";
4import { idResolver } from "app/(home-pages)/reader/idResolver";
5import { fetchAtprotoBlob } from "app/api/atproto_images/route";
6import { normalizeDocumentRecord } from "src/utils/normalizeRecords";
7+import { documentUriFilter } from "src/utils/uriHelpers";
89export const revalidate = 60;
10···2728 if (did) {
29 // Try to get the document's cover image
30+ let { data: documents } = await supabaseServerClient
31 .from("documents")
32 .select("data")
33+ .or(documentUriFilter(did, params.rkey))
34+ .order("uri", { ascending: false })
35+ .limit(1);
36+ let document = documents?.[0];
3738 if (document) {
39 const docRecord = normalizeDocumentRecord(jsonToLex(document.data));
+8-13
app/p/[didOrHandle]/[rkey]/page.tsx
···1import { supabaseServerClient } from "supabase/serverClient";
2-import { AtUri } from "@atproto/syntax";
3-import { ids } from "lexicons/api/lexicons";
4import { Metadata } from "next";
5import { idResolver } from "app/(home-pages)/reader/idResolver";
6import { DocumentPageRenderer } from "app/lish/[did]/[publication]/[rkey]/DocumentPageRenderer";
7import { NotFoundLayout } from "components/PageLayouts/NotFoundLayout";
8import { normalizeDocumentRecord } from "src/utils/normalizeRecords";
0910export async function generateMetadata(props: {
11 params: Promise<{ didOrHandle: string; rkey: string }>;
···24 }
25 }
2627- let { data: document } = await supabaseServerClient
28 .from("documents")
29- .select("*, documents_in_publications(publications(*))")
30- .eq("uri", AtUri.make(did, ids.PubLeafletDocument, params.rkey))
31- .single();
003233 if (!document) return { title: "404" };
3435 const docRecord = normalizeDocumentRecord(document.data);
36 if (!docRecord) return { title: "404" };
3738- // For documents in publications, include publication name
39- let publicationName =
40- document.documents_in_publications[0]?.publications?.name;
41-42 return {
43 icons: {
44 other: {
···46 url: document.uri,
47 },
48 },
49- title: publicationName
50- ? `${docRecord.title} - ${publicationName}`
51- : docRecord.title,
52 description: docRecord?.description || "",
53 };
54}
···1+import { ids } from "lexicons/api/lexicons";
2+3+/**
4+ * Check if a collection is a document collection (either namespace).
5+ */
6+export function isDocumentCollection(collection: string): boolean {
7+ return (
8+ collection === ids.PubLeafletDocument ||
9+ collection === ids.SiteStandardDocument
10+ );
11+}
12+13+/**
14+ * Check if a collection is a publication collection (either namespace).
15+ */
16+export function isPublicationCollection(collection: string): boolean {
17+ return (
18+ collection === ids.PubLeafletPublication ||
19+ collection === ids.SiteStandardPublication
20+ );
21+}
22+23+/**
24+ * Check if a collection belongs to the site.standard namespace.
25+ */
26+export function isSiteStandardCollection(collection: string): boolean {
27+ return collection.startsWith("site.standard.");
28+}
29+30+/**
31+ * Check if a collection belongs to the pub.leaflet namespace.
32+ */
33+export function isPubLeafletCollection(collection: string): boolean {
34+ return collection.startsWith("pub.leaflet.");
35+}
36+37+/**
38+ * Get the document $type to use based on an existing URI's collection.
39+ * If no existing URI or collection isn't a document, defaults to site.standard.document.
40+ */
41+export function getDocumentType(existingCollection?: string): "pub.leaflet.document" | "site.standard.document" {
42+ if (existingCollection === ids.PubLeafletDocument) {
43+ return ids.PubLeafletDocument as "pub.leaflet.document";
44+ }
45+ return ids.SiteStandardDocument as "site.standard.document";
46+}
47+48+/**
49+ * Get the publication $type to use based on an existing URI's collection.
50+ * If no existing URI or collection isn't a publication, defaults to site.standard.publication.
51+ */
52+export function getPublicationType(existingCollection?: string): "pub.leaflet.publication" | "site.standard.publication" {
53+ if (existingCollection === ids.PubLeafletPublication) {
54+ return ids.PubLeafletPublication as "pub.leaflet.publication";
55+ }
56+ return ids.SiteStandardPublication as "site.standard.publication";
57+}
+6-2
src/utils/mentionUtils.ts
···1import { AtUri } from "@atproto/api";
000023/**
4 * Converts a DID to a Bluesky profile URL
···14 try {
15 const uri = new AtUri(atUri);
1617- if (uri.collection === "pub.leaflet.publication") {
18 // Publication URL: /lish/{did}/{rkey}
19 return `/lish/${uri.host}/${uri.rkey}`;
20- } else if (uri.collection === "pub.leaflet.document") {
21 // Document URL - we need to resolve this via the API
22 // For now, create a redirect route that will handle it
23 return `/lish/uri/${encodeURIComponent(atUri)}`;
···1import { AtUri } from "@atproto/api";
2+import {
3+ isDocumentCollection,
4+ isPublicationCollection,
5+} from "src/utils/collectionHelpers";
67/**
8 * Converts a DID to a Bluesky profile URL
···18 try {
19 const uri = new AtUri(atUri);
2021+ if (isPublicationCollection(uri.collection)) {
22 // Publication URL: /lish/{did}/{rkey}
23 return `/lish/${uri.host}/${uri.rkey}`;
24+ } else if (isDocumentCollection(uri.collection)) {
25 // Document URL - we need to resolve this via the API
26 // For now, create a redirect route that will handle it
27 return `/lish/uri/${encodeURIComponent(atUri)}`;
+34
src/utils/uriHelpers.ts
···0000000000000000000000000000000000
···1+import { AtUri } from "@atproto/syntax";
2+import { ids } from "lexicons/api/lexicons";
3+4+/**
5+ * Returns an OR filter string for Supabase queries to match either namespace URI.
6+ * Used for querying documents that may be stored under either pub.leaflet.document
7+ * or site.standard.document namespaces.
8+ */
9+export function documentUriFilter(did: string, rkey: string): string {
10+ const standard = AtUri.make(did, ids.SiteStandardDocument, rkey).toString();
11+ const legacy = AtUri.make(did, ids.PubLeafletDocument, rkey).toString();
12+ return `uri.eq.${standard},uri.eq.${legacy}`;
13+}
14+15+/**
16+ * Returns an OR filter string for Supabase queries to match either namespace URI.
17+ * Used for querying publications that may be stored under either pub.leaflet.publication
18+ * or site.standard.publication namespaces.
19+ */
20+export function publicationUriFilter(did: string, rkey: string): string {
21+ const standard = AtUri.make(did, ids.SiteStandardPublication, rkey).toString();
22+ const legacy = AtUri.make(did, ids.PubLeafletPublication, rkey).toString();
23+ return `uri.eq.${standard},uri.eq.${legacy}`;
24+}
25+26+/**
27+ * Returns an OR filter string for Supabase queries to match a publication by name
28+ * or by either namespace URI. Used when the rkey might be the publication name.
29+ */
30+export function publicationNameOrUriFilter(did: string, nameOrRkey: string): string {
31+ const standard = AtUri.make(did, ids.SiteStandardPublication, nameOrRkey).toString();
32+ const legacy = AtUri.make(did, ids.PubLeafletPublication, nameOrRkey).toString();
33+ return `name.eq."${nameOrRkey}",uri.eq.${standard},uri.eq.${legacy}`;
34+}