Browse Source

refactor: Eliminate code duplication in publishing handlers

- Extract shared result processing logic into processPublishResults utility
- Add ProcessedPublishResults interface for type safety
- Create helper functions for error handling and event logging
- Reduce publishing handler code from ~200 to ~50 lines each
- Maintain all existing functionality while improving maintainability

Both handlePublishArticle and handlePublishScatteredNotes now use:
- processPublishResults() for consistent result processing
- createErrorResult() for standardized error handling
- logEventSummary() for centralized debug logging

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
master
limina1 6 months ago
parent
commit
de8fb36f41
  1. 167
      src/lib/services/publisher.ts
  2. 201
      src/routes/new/compose/+page.svelte

167
src/lib/services/publisher.ts

@ -1,10 +1,6 @@ @@ -1,10 +1,6 @@
import { getMimeTags } from "../utils/mime.ts";
import {
metadataToTags,
} from "../utils/asciidoc_metadata.ts";
import {
parseAsciiDocWithMetadata,
} from "../utils/asciidoc_parser.ts";
import { metadataToTags } from "../utils/asciidoc_metadata.ts";
import { parseAsciiDocWithMetadata } from "../utils/asciidoc_parser.ts";
import NDK, { NDKEvent, NDKRelaySet } from "@nostr-dev-kit/ndk";
import { nip19 } from "nostr-tools";
@ -14,6 +10,14 @@ export interface PublishResult { @@ -14,6 +10,14 @@ export interface PublishResult {
error?: string;
}
export interface ProcessedPublishResults {
successCount: number;
total: number;
errors: string[];
successfulEvents: Array<{ eventId: string; title: string }>;
failedEvents: Array<{ title: string; error: string; sectionIndex: number }>;
}
export interface PublishOptions {
content: string;
kind?: number;
@ -98,9 +102,8 @@ export async function publishZettel( @@ -98,9 +102,8 @@ export async function publishZettel(
throw new Error("Failed to publish to any relays");
}
} catch (error) {
const errorMessage = error instanceof Error
? error.message
: "Unknown error";
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
onError?.(errorMessage);
return { success: false, error: errorMessage };
}
@ -122,43 +125,56 @@ export async function publishSingleEvent( @@ -122,43 +125,56 @@ export async function publishSingleEvent(
): Promise<PublishResult> {
const { content, kind, tags, onError } = options;
if (!ndk?.activeUser) {
const error = 'Please log in first';
const error = "Please log in first";
onError?.(error);
return { success: false, error };
}
try {
const allRelayUrls = Array.from(ndk.pool?.relays.values() || []).map((r) => r.url);
const allRelayUrls = Array.from(ndk.pool?.relays.values() || []).map(
(r) => r.url,
);
if (allRelayUrls.length === 0) {
throw new Error('No relays available in NDK pool');
throw new Error("No relays available in NDK pool");
}
const relaySet = NDKRelaySet.fromRelayUrls(allRelayUrls, ndk);
// Fix a-tags that have placeholder "pubkey" with actual pubkey
const fixedTags = tags.map(tag => {
if (tag[0] === 'a' && tag[1] && tag[1].includes(':pubkey:') && ndk.activeUser) {
const fixedTags = tags.map((tag) => {
if (
tag[0] === "a" &&
tag[1] &&
tag[1].includes(":pubkey:") &&
ndk.activeUser
) {
// Replace "pubkey" placeholder with actual pubkey
const fixedATag = tag[1].replace(':pubkey:', `:${ndk.activeUser.pubkey}:`);
return [tag[0], fixedATag, tag[2] || '', tag[3] || ''];
const fixedATag = tag[1].replace(
":pubkey:",
`:${ndk.activeUser.pubkey}:`,
);
return [tag[0], fixedATag, tag[2] || "", tag[3] || ""];
}
return tag;
});
// Auto-add author identity if not publishing on behalf of others
const hasAuthorTag = fixedTags.some(tag => tag[0] === 'author');
const hasPTag = fixedTags.some(tag => tag[0] === 'p');
const hasAuthorTag = fixedTags.some((tag) => tag[0] === "author");
const hasPTag = fixedTags.some((tag) => tag[0] === "p");
const finalTags = [...fixedTags];
if (!hasAuthorTag && ndk.activeUser) {
// Add display name as author
const displayName = ndk.activeUser.profile?.displayName || ndk.activeUser.profile?.name || 'Anonymous';
finalTags.push(['author', displayName]);
const displayName =
ndk.activeUser.profile?.displayName ||
ndk.activeUser.profile?.name ||
"Anonymous";
finalTags.push(["author", displayName]);
}
if (!hasPTag && ndk.activeUser) {
// Add pubkey as p-tag
finalTags.push(['p', ndk.activeUser.pubkey]);
finalTags.push(["p", ndk.activeUser.pubkey]);
}
// Create and sign NDK event
@ -176,22 +192,25 @@ export async function publishSingleEvent( @@ -176,22 +192,25 @@ export async function publishSingleEvent(
if (publishedToRelays.size > 0) {
// Debug: Log the event structure in a clean, concise format
const dTagEntry = tags.find(t => t[0] === 'd');
const dTag = dTagEntry ? dTagEntry[1] : '';
const titleTag = tags.find(t => t[0] === 'title');
const title = titleTag ? titleTag[1] : 'Untitled';
const dTagEntry = tags.find((t) => t[0] === "d");
const dTag = dTagEntry ? dTagEntry[1] : "";
const titleTag = tags.find((t) => t[0] === "title");
const title = titleTag ? titleTag[1] : "Untitled";
console.log(`Event verified: ${ndkEvent.id}`);
return { success: true, eventId: ndkEvent.id };
} else {
const titleTag = tags.find(t => t[0] === 'title');
const title = titleTag ? titleTag[1] : 'Untitled';
console.error(`Failed to publish event: ${title} (${kind}) - no relays responded`);
throw new Error('Failed to publish to any relays');
const titleTag = tags.find((t) => t[0] === "title");
const title = titleTag ? titleTag[1] : "Untitled";
console.error(
`Failed to publish event: ${title} (${kind}) - no relays responded`,
);
throw new Error("Failed to publish to any relays");
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
console.error(`Error publishing event: ${errorMessage}`);
onError?.(errorMessage);
return { success: false, error: errorMessage };
@ -227,8 +246,8 @@ export async function publishMultipleZettels( @@ -227,8 +246,8 @@ export async function publishMultipleZettels(
throw new Error("No valid sections found in content");
}
const allRelayUrls = Array.from(ndk.pool?.relays.values() || []).map((r) =>
r.url
const allRelayUrls = Array.from(ndk.pool?.relays.values() || []).map(
(r) => r.url,
);
if (allRelayUrls.length === 0) {
throw new Error("No relays available in NDK pool");
@ -266,9 +285,8 @@ export async function publishMultipleZettels( @@ -266,9 +285,8 @@ export async function publishMultipleZettels(
});
}
} catch (err) {
const errorMessage = err instanceof Error
? err.message
: "Unknown error";
const errorMessage =
err instanceof Error ? err.message : "Unknown error";
results.push({ success: false, error: errorMessage });
}
}
@ -293,14 +311,87 @@ export async function publishMultipleZettels( @@ -293,14 +311,87 @@ export async function publishMultipleZettels(
});
return results;
} catch (error) {
const errorMessage = error instanceof Error
? error.message
: "Unknown error";
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
onError?.(errorMessage);
return [{ success: false, error: errorMessage }];
}
}
/**
* Processes publish results and extracts success/failure information
* @param results - Array of publish results
* @param events - Event objects containing content and metadata
* @param hasIndexEvent - Whether the events include an index event
* @returns Processed results with counts and event details
*/
export function processPublishResults(
results: PublishResult[],
events: { indexEvent?: any; contentEvents: any[] },
hasIndexEvent: boolean = false,
): ProcessedPublishResults {
const successCount = results.filter((r) => r.success).length;
const errors = results
.filter((r) => !r.success && r.error)
.map((r) => r.error!);
// Extract successful events with their titles
const successfulEvents = results
.filter((r) => r.success && r.eventId)
.map((r, index) => {
let title: string;
if (index === 0 && hasIndexEvent && events.indexEvent) {
title = "Article Index";
} else {
const contentIndex = hasIndexEvent ? index - 1 : index;
const contentEvent = events.contentEvents[contentIndex];
title =
contentEvent?.title ||
contentEvent?.tags?.find((t: any) => t[0] === "title")?.[1] ||
`Note ${contentIndex + 1}`;
}
return {
eventId: r.eventId!,
title,
};
});
// Extract failed events with their titles and errors
const failedEvents = results
.map((r, index) => ({ result: r, index }))
.filter(({ result }) => !result.success)
.map(({ result, index }) => {
let title: string;
if (index === 0 && hasIndexEvent && events.indexEvent) {
title = "Article Index";
} else {
const contentIndex = hasIndexEvent ? index - 1 : index;
const contentEvent = events.contentEvents[contentIndex];
title =
contentEvent?.title ||
contentEvent?.tags?.find((t: any) => t[0] === "title")?.[1] ||
`Note ${contentIndex + 1}`;
}
return {
title,
error: result.error || "Unknown error",
sectionIndex: index,
};
});
return {
successCount,
total: results.length,
errors,
successfulEvents,
failedEvents,
};
}
function generateDTag(title: string): string {
return title
.toLowerCase()

201
src/routes/new/compose/+page.svelte

@ -2,7 +2,11 @@ @@ -2,7 +2,11 @@
import { Heading, Button, Alert } from "flowbite-svelte";
import ZettelEditor from "$lib/components/ZettelEditor.svelte";
import { nip19 } from "nostr-tools";
import { publishSingleEvent } from "$lib/services/publisher";
import {
publishSingleEvent,
processPublishResults,
type ProcessedPublishResults,
} from "$lib/services/publisher";
import { getNdkContext } from "$lib/ndk";
const ndk = getNdkContext();
@ -10,13 +14,7 @@ @@ -10,13 +14,7 @@
let content = $state("");
let showPreview = $state(false);
let isPublishing = $state(false);
let publishResults = $state<{
successCount: number;
total: number;
errors: string[];
successfulEvents: Array<{ eventId: string; title: string }>;
failedEvents: Array<{ title: string; error: string; sectionIndex: number }>;
} | null>(null);
let publishResults = $state<ProcessedPublishResults | null>(null);
// Handle content changes from ZettelEditor
function handleContentChange(newContent: string) {
@ -28,6 +26,53 @@ @@ -28,6 +26,53 @@
showPreview = show;
}
// Helper function to create error result
function createErrorResult(error: unknown): ProcessedPublishResults {
return {
successCount: 0,
total: 0,
errors: [error instanceof Error ? error.message : "Unknown error"],
successfulEvents: [],
failedEvents: [],
};
}
// Helper function to log event summaries
function logEventSummary(
events: any,
successfulEvents: Array<{ eventId: string; title: string }>,
) {
console.log("\n=== Events Summary ===");
if (events.indexEvent) {
console.log("\nRoot Index:");
console.log(`Event Summary:`);
console.log(` ID: ${successfulEvents[0]?.eventId || "Failed"}`);
console.log(` Kind: 30040`);
console.log(` Tags:`);
events.indexEvent.tags.forEach((tag: string[]) => {
console.log(` - ${JSON.stringify(tag)}`);
});
console.log(" ---");
}
console.log("\nContent:");
events.contentEvents.forEach((event: any, index: number) => {
const eventId =
successfulEvents.find((e) => e.title === event.title)?.eventId ||
"Failed";
console.log(`\nEvent Summary:`);
console.log(` ID: ${eventId}`);
console.log(` Kind: 30041`);
console.log(` Tags:`);
event.tags.forEach((tag: any) => {
console.log(` - ${JSON.stringify(tag)}`);
});
console.log(` Content preview: ${event.content.substring(0, 100)}...`);
console.log(" ---");
});
}
// Handle unified publishing from ZettelEditor
async function handlePublishArticle(events: any) {
isPublishing = true;
@ -82,84 +127,18 @@ @@ -82,84 +127,18 @@
results.push(result);
}
// Process results
const successCount = results.filter((r) => r.success).length;
const errors = results
.filter((r) => !r.success && r.error)
.map((r) => r.error!);
// Extract successful events with their titles
const successfulEvents = results
.filter((r) => r.success && r.eventId)
.map((r, index) => ({
eventId: r.eventId!,
title:
index === 0 && events.indexEvent
? "Article Index"
: events.contentEvents[index - (events.indexEvent ? 1 : 0)]
?.title || `Note ${index}`,
}));
// Extract failed events with their titles and errors
const failedEvents = results
.map((r, index) => ({ result: r, index }))
.filter(({ result }) => !result.success)
.map(({ result, index }) => ({
title:
index === 0 && events.indexEvent
? "Article Index"
: events.contentEvents[index - (events.indexEvent ? 1 : 0)]
?.title || `Note ${index}`,
error: result.error || "Unknown error",
sectionIndex: index,
}));
publishResults = {
successCount,
total: results.length,
errors,
successfulEvents,
failedEvents,
};
// Process results using shared utility
publishResults = processPublishResults(
results,
events,
!!events.indexEvent,
);
// Show summary
console.log("\n=== Events Summary ===");
if (events.indexEvent) {
console.log("\nRoot Index:");
console.log(`Event Summary:`);
console.log(` ID: ${successfulEvents[0]?.eventId || "Failed"}`);
console.log(` Kind: 30040`);
console.log(` Tags:`);
events.indexEvent.tags.forEach((tag: string[]) => {
console.log(` - ${JSON.stringify(tag)}`);
});
console.log(" ---");
}
console.log("\nContent:");
events.contentEvents.forEach((event: any, index: number) => {
const eventId =
successfulEvents.find((e) => e.title === event.title)?.eventId ||
"Failed";
console.log(`\nEvent Summary:`);
console.log(` ID: ${eventId}`);
console.log(` Kind: 30041`);
console.log(` Tags:`);
event.tags.forEach((tag: any) => {
console.log(` - ${JSON.stringify(tag)}`);
});
console.log(` Content preview: ${event.content.substring(0, 100)}...`);
console.log(" ---");
});
logEventSummary(events, publishResults.successfulEvents);
} catch (error) {
console.error("Publishing failed:", error);
publishResults = {
successCount: 0,
total: 0,
errors: [error instanceof Error ? error.message : "Unknown error"],
successfulEvents: [],
failedEvents: [],
};
publishResults = createErrorResult(error);
}
isPublishing = false;
@ -193,64 +172,14 @@ @@ -193,64 +172,14 @@
results.push(result);
}
// Process results
const successCount = results.filter((r) => r.success).length;
const errors = results
.filter((r) => !r.success && r.error)
.map((r) => r.error!);
// Extract successful events with their titles
const successfulEvents = results
.filter((r) => r.success && r.eventId)
.map((r, index) => ({
eventId: r.eventId!,
title: events.contentEvents[index]?.title || `Note ${index + 1}`,
}));
// Extract failed events with their titles and errors
const failedEvents = results
.map((r, index) => ({ result: r, index }))
.filter(({ result }) => !result.success)
.map(({ result, index }) => ({
title: events.contentEvents[index]?.title || `Note ${index + 1}`,
error: result.error || "Unknown error",
sectionIndex: index,
}));
publishResults = {
successCount,
total: results.length,
errors,
successfulEvents,
failedEvents,
};
// Process results using shared utility
publishResults = processPublishResults(results, events, false);
// Show summary
console.log("\n=== Events Summary ===");
console.log("\nContent:");
events.contentEvents.forEach((event: any, index: number) => {
const eventId =
successfulEvents.find((e) => e.title === event.title)?.eventId ||
"Failed";
console.log(`\nEvent Summary:`);
console.log(` ID: ${eventId}`);
console.log(` Kind: 30041`);
console.log(` Tags:`);
event.tags.forEach((tag: any) => {
console.log(` - ${JSON.stringify(tag)}`);
});
console.log(` Content preview: ${event.content.substring(0, 100)}...`);
console.log(" ---");
});
logEventSummary(events, publishResults.successfulEvents);
} catch (error) {
console.error("Publishing failed:", error);
publishResults = {
successCount: 0,
total: 0,
errors: [error instanceof Error ? error.message : "Unknown error"],
successfulEvents: [],
failedEvents: [],
};
publishResults = createErrorResult(error);
}
isPublishing = false;

Loading…
Cancel
Save