fix: auto-publish approved blog submissions on approval
Approved user submissions now import into the blog automatically, with a backfill script for existing approvals and a WebSocket polyfill so backend Nostr relay queries work on Node 20. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import { prisma } from '../db/prisma';
|
||||
import { nostrService } from './nostr';
|
||||
|
||||
export interface ImportPostInput {
|
||||
nostrEventId: string;
|
||||
naddr?: string | null;
|
||||
title: string;
|
||||
excerpt?: string | null;
|
||||
authorPubkey: string;
|
||||
publishedAt?: number | string | null;
|
||||
tags?: string[];
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
// Upserts a blog Post from a Nostr longform event. Shared by the manual import
|
||||
// endpoint and the submission-approval flow so both produce identical results
|
||||
// (auto slug, category links from `t` tags, visible by default).
|
||||
export async function importPostFromNostr(input: ImportPostInput) {
|
||||
const { nostrEventId, naddr, title, excerpt, authorPubkey, publishedAt, tags, visible } = input;
|
||||
|
||||
const slugBase = title
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-|-$/g, '');
|
||||
const slug = `${slugBase}-${nostrEventId.slice(0, 8)}`;
|
||||
|
||||
const post = await prisma.post.upsert({
|
||||
where: { nostrEventId },
|
||||
update: {
|
||||
title,
|
||||
excerpt: excerpt || undefined,
|
||||
naddr: naddr || undefined,
|
||||
},
|
||||
create: {
|
||||
nostrEventId,
|
||||
naddr: naddr || null,
|
||||
title,
|
||||
slug,
|
||||
excerpt: excerpt || null,
|
||||
authorPubkey,
|
||||
visible: visible ?? true,
|
||||
publishedAt: publishedAt
|
||||
? new Date(typeof publishedAt === 'number' ? publishedAt * 1000 : publishedAt)
|
||||
: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
if (Array.isArray(tags) && tags.length > 0) {
|
||||
const categoryIds: string[] = [];
|
||||
for (const tag of tags) {
|
||||
const catSlug = tag.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
||||
if (!catSlug) continue;
|
||||
const category = await prisma.category.upsert({
|
||||
where: { slug: catSlug },
|
||||
update: {},
|
||||
create: {
|
||||
name: tag.charAt(0).toUpperCase() + tag.slice(1),
|
||||
slug: catSlug,
|
||||
},
|
||||
});
|
||||
categoryIds.push(category.id);
|
||||
}
|
||||
|
||||
await prisma.postCategory.deleteMany({ where: { postId: post.id } });
|
||||
if (categoryIds.length > 0) {
|
||||
await prisma.postCategory.createMany({
|
||||
data: categoryIds.map((categoryId) => ({
|
||||
postId: post.id,
|
||||
categoryId,
|
||||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return prisma.post.findUnique({
|
||||
where: { id: post.id },
|
||||
include: { categories: { include: { category: true } } },
|
||||
});
|
||||
}
|
||||
|
||||
export interface SubmissionRef {
|
||||
eventId: string | null;
|
||||
naddr: string | null;
|
||||
title: string;
|
||||
authorPubkey: string;
|
||||
}
|
||||
|
||||
// Resolves the Nostr longform event referenced by a submission into a blog
|
||||
// import payload. Returns null when the event cannot be resolved into something
|
||||
// publishable (e.g. naddr that no relay can serve and no fallback event id).
|
||||
export async function resolveSubmissionImport(
|
||||
submission: SubmissionRef
|
||||
): Promise<ImportPostInput | null> {
|
||||
const event = submission.naddr
|
||||
? await nostrService.fetchLongformEvent(submission.naddr)
|
||||
: submission.eventId
|
||||
? await nostrService.fetchEvent(submission.eventId)
|
||||
: null;
|
||||
|
||||
const nostrEventId: string | undefined = event?.id || submission.eventId || undefined;
|
||||
if (!nostrEventId) return null;
|
||||
|
||||
const eventTags: string[][] = Array.isArray(event?.tags) ? (event!.tags as string[][]) : [];
|
||||
const titleTag = eventTags.find((t) => t[0] === 'title')?.[1];
|
||||
const topicTags = eventTags
|
||||
.filter((t) => t[0] === 't' && t[1])
|
||||
.map((t) => (t[1] as string).toLowerCase());
|
||||
|
||||
const excerpt = ((event?.content as string) || '')
|
||||
.slice(0, 200)
|
||||
.replace(/[#*_\n]/g, '')
|
||||
.trim();
|
||||
|
||||
return {
|
||||
nostrEventId,
|
||||
naddr: submission.naddr || undefined,
|
||||
title: submission.title || titleTag || 'Untitled',
|
||||
excerpt: excerpt || undefined,
|
||||
authorPubkey: event?.pubkey || submission.authorPubkey,
|
||||
publishedAt: event?.created_at ?? null,
|
||||
tags: topicTags.length > 0 ? topicTags : undefined,
|
||||
visible: true,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user