Keep listed published rules public and guest-claim rules unlisted, with semantic document markup and share chrome that stays clickable.

Guest publishes remain share-by-link but stay out of the catalog and sitemap, and their pages send noindex until claimed. The published-rule view now uses heading and list markup matching Markdown export, and top banners sit below the header so Share is not covered.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
adilalloandCursor committed 2026-09-17 17:42:01 -06:00
1 parent 77b0ec8ad8
commit 1445cb7e1e
27 files changed
+524 -86

No files matched your search

+74 -3
View File
@@ -1,9 +1,14 @@
import {
LISTED_PUBLISHED_RULE_WHERE,
isListedPublishedRule,
} from "../publishedRuleAccess";
import { prisma } from "./db";
import { isDatabaseConfigured } from "./env";
/**
* Public fields safe to expose via the unauthenticated rule detail surfaces
* (`GET /api/rules/[id]` and `/rules/[id]`). `userId` is intentionally omitted.
* (`GET /api/rules/[id]` and `/rules/[id]`). `userId` and `claimTokenHash`
* stay server-side; callers get {@link PublicPublishedRule.isListed} instead.
*/
const PUBLISHED_RULE_PUBLIC_SELECT = {
id: true,
@@ -21,6 +26,8 @@ export type PublicPublishedRule = {
document: unknown;
createdAt: Date;
updatedAt: Date;
/** False for guest-claim rows; true for owned and orphaned-after-delete rows. */
isListed: boolean;
};
/**
@@ -39,9 +46,18 @@ export async function getPublicPublishedRuleById(
try {
const rule = await prisma.publishedRule.findUnique({
where: { id },
select: PUBLISHED_RULE_PUBLIC_SELECT,
select: {
...PUBLISHED_RULE_PUBLIC_SELECT,
userId: true,
claimTokenHash: true,
},
});
return rule;
if (!rule) return null;
const { userId, claimTokenHash, ...publicFields } = rule;
return {
...publicFields,
isListed: isListedPublishedRule({ userId, claimTokenHash }),
};
} catch {
return null;
}
@@ -64,6 +80,61 @@ export type OwnerPublishedRuleListItem = {
updatedAt: Date;
};
const LISTED_RULE_SITEMAP_SELECT = {
id: true,
updatedAt: true,
} as const;
const LISTED_PUBLISHED_RULE_SITEMAP_CAP = 10_000;
/**
* Public catalog of listed published rules (**most recently updated first**,
* then stable `id` tie-break). Guest-claim rows are omitted.
* Returns `null` when the database is not configured or the query throws.
*/
export async function listListedPublishedRules(
take: number,
): Promise<OwnerPublishedRuleListItem[] | null> {
if (!isDatabaseConfigured()) return null;
const clamped = Math.min(Math.max(0, take), 100);
if (clamped === 0) return [];
try {
return await prisma.publishedRule.findMany({
where: LISTED_PUBLISHED_RULE_WHERE,
orderBy: [{ updatedAt: "desc" }, { id: "asc" }],
take: clamped,
select: PUBLISHED_RULE_OWNER_LIST_SELECT,
});
} catch {
return null;
}
}
/**
* Listed published-rule ids for `/sitemap.xml`. Empty when the database is
* not configured or the query throws (marketing URLs still render).
*/
export async function listListedPublishedRulesForSitemap(
take: number = LISTED_PUBLISHED_RULE_SITEMAP_CAP,
): Promise<Array<{ id: string; updatedAt: Date }>> {
if (!isDatabaseConfigured()) return [];
const clamped = Math.min(
Math.max(0, take),
LISTED_PUBLISHED_RULE_SITEMAP_CAP,
);
if (clamped === 0) return [];
try {
return await prisma.publishedRule.findMany({
where: LISTED_PUBLISHED_RULE_WHERE,
orderBy: [{ updatedAt: "desc" }, { id: "asc" }],
take: clamped,
select: LISTED_RULE_SITEMAP_SELECT,
});
} catch {
return [];
}
}
/**
* Lists published rules owned by the given user (**most recently updated first**,
* then stable `id` tie-break).