Browse Source

Clean up and refactor based on AI code review

master
buttercat1791 8 months ago
parent
commit
2bb42d3ec4
  1. 8
      src/lib/components/publications/PublicationFeed.svelte
  2. 2
      src/lib/navigator/EventNetwork/NodeTooltip.svelte
  3. 63
      src/lib/utils.ts
  4. 3
      src/lib/utils/event_search.ts
  5. 6
      src/lib/utils/markup/advancedAsciidoctorPostProcessor.ts
  6. 5
      src/lib/utils/network_detection.ts
  7. 83
      src/lib/utils/nostrUtils.ts
  8. 2
      src/lib/utils/search_types.ts
  9. 21
      src/routes/+layout.ts
  10. 33
      src/routes/publication/+page.server.ts
  11. 89
      src/routes/publication/[type]/[identifier]/+layout.server.ts
  12. 89
      src/routes/publication/[type]/[identifier]/+page.server.ts

8
src/lib/components/publications/PublicationFeed.svelte

@ -290,9 +290,9 @@ @@ -290,9 +290,9 @@
};
// Debounced search function
const debouncedSearch = debounce(async (query: string) => {
const debouncedSearch = debounce((query: string | undefined) => {
console.debug("[PublicationFeed] Search query changed:", query);
if (query.trim()) {
if (query && query.trim()) {
const filtered = filterEventsBySearch(allIndexEvents);
eventsInView = filtered.slice(0, 30);
endOfFeed = filtered.length <= 30;
@ -303,10 +303,6 @@ @@ -303,10 +303,6 @@
}, 300);
$effect(() => {
console.debug(
"[PublicationFeed] Search query effect triggered:",
props.searchQuery,
);
debouncedSearch(props.searchQuery);
});

2
src/lib/navigator/EventNetwork/NodeTooltip.svelte

@ -145,7 +145,7 @@ @@ -145,7 +145,7 @@
<div class="tooltip-content">
<!-- Title with link -->
<div class="tooltip-title">
<a href="/publication/id/{node.id}" class="tooltip-title-link">
<a href={`/publication/id/${node.id}`} class="tooltip-title-link">
{node.title || "Untitled"}
</a>
</div>

63
src/lib/utils.ts

@ -1,7 +1,21 @@ @@ -1,7 +1,21 @@
import type { NDKEvent } from "@nostr-dev-kit/ndk";
import { nip19 } from "nostr-tools";
import { getMatchingTags } from "./utils/nostrUtils.ts";
import { AddressPointer, EventPointer } from "nostr-tools/nip19";
import type { AddressPointer, EventPointer } from "nostr-tools/nip19";
export class DecodeError extends Error {
constructor(message: string) {
super(message);
this.name = "DecodeError";
}
}
export class InvalidKindError extends DecodeError {
constructor(message: string) {
super(message);
this.name = "InvalidKindError";
}
}
export function neventEncode(event: NDKEvent, relays: string[]) {
return nip19.neventEncode({
@ -31,39 +45,41 @@ export function nprofileEncode(pubkey: string, relays: string[]) { @@ -31,39 +45,41 @@ export function nprofileEncode(pubkey: string, relays: string[]) {
}
/**
* Decodes an naddr identifier and returns the decoded data
* Decodes a nostr identifier (naddr, nevent) and returns the decoded data.
* @param identifier The nostr identifier to decode.
* @param expectedType The expected type of the decoded data ('naddr' or 'nevent').
* @returns The decoded data.
*/
export function naddrDecode(naddr: string): AddressPointer {
function decodeNostrIdentifier<T extends AddressPointer | EventPointer>(
identifier: string,
expectedType: "naddr" | "nevent",
): T {
try {
if (!naddr.startsWith('naddr')) {
throw new Error('Invalid naddr format');
if (!identifier.startsWith(expectedType)) {
throw new InvalidKindError(`Invalid ${expectedType} format`);
}
const decoded = nip19.decode(naddr);
if (decoded.type !== 'naddr') {
throw new Error('Decoded result is not an naddr');
const decoded = nip19.decode(identifier);
if (decoded.type !== expectedType) {
throw new InvalidKindError(`Decoded result is not an ${expectedType}`);
}
return decoded.data;
return decoded.data as T;
} catch (error) {
throw new Error(`Failed to decode naddr: ${error}`);
throw new DecodeError(`Failed to decode ${expectedType}: ${error}`);
}
}
/**
* Decodes an naddr identifier and returns the decoded data
*/
export function naddrDecode(naddr: string): AddressPointer {
return decodeNostrIdentifier<AddressPointer>(naddr, "naddr");
}
/**
* Decodes an nevent identifier and returns the decoded data
*/
export function neventDecode(nevent: string): EventPointer {
try {
if (!nevent.startsWith('nevent')) {
throw new Error('Invalid nevent format');
}
const decoded = nip19.decode(nevent);
if (decoded.type !== 'nevent') {
throw new Error('Decoded result is not an nevent');
}
return decoded.data;
} catch (error) {
throw new Error(`Failed to decode nevent: ${error}`);
}
return decodeNostrIdentifier<EventPointer>(nevent, "nevent");
}
export function formatDate(unixtimestamp: number) {
@ -206,7 +222,8 @@ Array.prototype.findIndexAsync = function <T>( @@ -206,7 +222,8 @@ Array.prototype.findIndexAsync = function <T>(
* @param wait The number of milliseconds to delay
* @returns A debounced version of the function
*/
export function debounce<T extends (...args: unknown[]) => unknown>(
// deno-lint-ignore no-explicit-any
export function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number,
): (...args: Parameters<T>) => void {

3
src/lib/utils/event_search.ts

@ -1,7 +1,8 @@ @@ -1,7 +1,8 @@
import { ndkInstance } from "../ndk.ts";
import { fetchEventWithFallback } from "./nostrUtils.ts";
import { nip19 } from "nostr-tools";
import { NDKEvent, NDKFilter } from "@nostr-dev-kit/ndk";
import type { NDKFilter } from "@nostr-dev-kit/ndk";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { get } from "svelte/store";
import { wellKnownUrl, isValidNip05Address } from "./search_utils.ts";
import { TIMEOUTS, VALIDATION } from "./search_constants.ts";

6
src/lib/utils/markup/advancedAsciidoctorPostProcessor.ts

@ -32,9 +32,11 @@ export async function postProcessAdvancedAsciidoctorHtml( @@ -32,9 +32,11 @@ export async function postProcessAdvancedAsciidoctorHtml(
}
if (
typeof globalThis !== "undefined" &&
typeof globalThis.MathJax?.typesetPromise === "function"
// deno-lint-ignore no-explicit-any
typeof (globalThis as any).MathJax?.typesetPromise === "function"
) {
setTimeout(() => globalThis.MathJax.typesetPromise(), 0);
// deno-lint-ignore no-explicit-any
setTimeout(() => (globalThis as any).MathJax.typesetPromise(), 0);
}
return processedHtml;
} catch (error) {

5
src/lib/utils/network_detection.ts

@ -153,10 +153,11 @@ export function getRelaySetForNetworkCondition( @@ -153,10 +153,11 @@ export function getRelaySetForNetworkCondition(
*/
export function startNetworkMonitoring(
onNetworkChange: (condition: NetworkCondition) => void,
checkInterval: number = 60000 // Increased to 60 seconds to reduce spam
checkInterval: number = 60000, // Increased to 60 seconds to reduce spam
): () => void {
let lastCondition: NetworkCondition | null = null;
let intervalId: number | null = null;
// deno-lint-ignore no-explicit-any
let intervalId: any = null;
const checkNetwork = async () => {
try {

83
src/lib/utils/nostrUtils.ts

@ -12,6 +12,8 @@ import { schnorr } from "@noble/curves/secp256k1"; @@ -12,6 +12,8 @@ import { schnorr } from "@noble/curves/secp256k1";
import { bytesToHex } from "@noble/hashes/utils";
import { wellKnownUrl } from "./search_utility.ts";
import { VALIDATION } from "./search_constants.ts";
import { error } from "@sveltejs/kit";
import { naddrDecode, neventDecode } from "../utils.ts";
const badgeCheckSvg =
'<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M12 2c-.791 0-1.55.314-2.11.874l-.893.893a.985.985 0 0 1-.696.288H7.04A2.984 2.984 0 0 0 4.055 7.04v1.262a.986.986 0 0 1-.288.696l-.893.893a2.984 2.984 0 0 0 0 4.22l.893.893a.985.985 0 0 1 .288.696v1.262a2.984 2.984 0 0 0 2.984 2.984h1.262c.261 0 .512.104.696.288l.893.893a2.984 2.984 0 0 0 4.22 0l.893-.893a.985.985 0 0 1 .696-.288h1.262a2.984 2.984 0 0 0 2.984-2.984V15.7c0-.261.104-.512.288-.696l.893-.893a2.984 2.984 0 0 0 0-4.22l-.893-.893a.985.985 0 0 1-.288-.696V7.04a2.984 2.984 0 0 0-2.984-2.984h-1.262a.985.985 0 0 1-.696-.288l-.893-.893A2.984 2.984 0 0 0 12 2Zm3.683 7.73a1 1 0 1 0-1.414-1.413l-4.253 4.253-1.277-1.277a1 1 0 0 0-1.415 1.414l1.985 1.984a1 1 0 0 0 1.414 0l4.96-4.96Z" clip-rule="evenodd"/></svg>';
@ -668,3 +670,84 @@ export function prefixNostrAddresses(content: string): string { @@ -668,3 +670,84 @@ export function prefixNostrAddresses(content: string): string {
return `nostr:${match}`;
});
}
// Added functions for fetching events by various identifiers
/**
* Fetches an event by hex ID, throwing a SvelteKit 404 error if not found.
*/
export async function fetchEventById(ndk: NDK, id: string): Promise<NDKEvent> {
try {
const event = await fetchEventWithFallback(ndk, id);
if (!event) {
throw error(404, `Event not found for ID: ${id}`);
}
return event;
} catch (err) {
if (err && typeof err === "object" && "status" in err) {
throw err;
}
throw error(404, `Failed to fetch event by ID: ${err}`);
}
}
/**
* Fetches an event by d tag, throwing a 404 if not found.
*/
export async function fetchEventByDTag(ndk: NDK, dTag: string): Promise<NDKEvent> {
try {
const event = await fetchEventWithFallback(ndk, { "#d": [dTag], limit: 1 });
if (!event) {
throw error(404, `Event not found for d-tag: ${dTag}`);
}
return event;
} catch (err) {
if (err && typeof err === "object" && "status" in err) {
throw err;
}
throw error(404, `Failed to fetch event by d-tag: ${err}`);
}
}
/**
* Fetches an event by naddr identifier.
*/
export async function fetchEventByNaddr(ndk: NDK, naddr: string): Promise<NDKEvent> {
try {
const decoded = naddrDecode(naddr);
const filter = {
kinds: [decoded.kind],
authors: [decoded.pubkey],
"#d": [decoded.identifier],
};
const event = await fetchEventWithFallback(ndk, filter);
if (!event) {
throw error(404, `Event not found for naddr: ${naddr}`);
}
return event;
} catch (err) {
if (err && typeof err === "object" && "status" in err) {
throw err;
}
throw error(404, `Failed to fetch event by naddr: ${err}`);
}
}
/**
* Fetches an event by nevent identifier.
*/
export async function fetchEventByNevent(ndk: NDK, nevent: string): Promise<NDKEvent> {
try {
const decoded = neventDecode(nevent);
const event = await fetchEventWithFallback(ndk, decoded.id);
if (!event) {
throw error(404, `Event not found for nevent: ${nevent}`);
}
return event;
} catch (err) {
if (err && typeof err === "object" && "status" in err) {
throw err;
}
throw error(404, `Failed to fetch event by nevent: ${err}`);
}
}

2
src/lib/utils/search_types.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { NDKEvent, NDKFilter, NDKSubscription } from "@nostr-dev-kit/ndk";
import type { NDKEvent, NDKFilter, NDKSubscription } from "@nostr-dev-kit/ndk";
/**
* Extended NostrProfile interface for search results

21
src/routes/+layout.ts

@ -8,12 +8,13 @@ import { loginMethodStorageKey } from "../lib/stores/userStore.ts"; @@ -8,12 +8,13 @@ import { loginMethodStorageKey } from "../lib/stores/userStore.ts";
import Pharos, { pharosInstance } from "../lib/parser.ts";
import type { LayoutLoad } from "./$types";
import { get } from "svelte/store";
import { browser } from "$app/environment";
export const load: LayoutLoad = () => {
// Initialize NDK with new relay management system
const ndk = initNdk();
ndkInstance.set(ndk);
/**
* Attempts to restore the user's authentication session from localStorage.
* Handles extension, Amber (NIP-46), and npub login methods.
*/
async function restoreAuthSession() {
try {
const pubkey = getPersistedLogin();
const loginMethod = localStorage.getItem(loginMethodStorageKey);
@ -111,6 +112,16 @@ export const load: LayoutLoad = () => { @@ -111,6 +112,16 @@ export const load: LayoutLoad = () => {
`Failed to restore login: ${e}\n\nContinuing with anonymous session.`,
);
}
}
export const load: LayoutLoad = () => {
// Initialize NDK with new relay management system
const ndk = initNdk();
ndkInstance.set(ndk);
if (browser) {
restoreAuthSession();
}
const parser = new Pharos(ndk);
pharosInstance.set(parser);

33
src/routes/publication/+page.server.ts

@ -1,6 +1,22 @@ @@ -1,6 +1,22 @@
import { redirect } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";
// Route pattern constants
const ROUTES = {
PUBLICATION_BASE: "/publication",
NADDR: "/publication/naddr",
NEVENT: "/publication/nevent",
ID: "/publication/id",
D_TAG: "/publication/d",
START: "/start",
} as const;
// Identifier prefixes
const IDENTIFIER_PREFIXES = {
NADDR: "naddr",
NEVENT: "nevent",
} as const;
export const load: PageServerLoad = ({ url }) => {
const id = url.searchParams.get("id");
const dTag = url.searchParams.get("d");
@ -8,19 +24,18 @@ export const load: PageServerLoad = ({ url }) => { @@ -8,19 +24,18 @@ export const load: PageServerLoad = ({ url }) => {
// Handle backward compatibility for old query-based routes
if (id) {
// Check if id is an naddr or nevent
if (id.startsWith("naddr")) {
throw redirect(301, `/publication/naddr/${id}`);
} else if (id.startsWith("nevent")) {
throw redirect(301, `/publication/nevent/${id}`);
if (id.startsWith(IDENTIFIER_PREFIXES.NADDR)) {
throw redirect(301, `${ROUTES.NADDR}/${id}`);
} else if (id.startsWith(IDENTIFIER_PREFIXES.NEVENT)) {
throw redirect(301, `${ROUTES.NEVENT}/${id}`);
} else {
// Assume it's a hex ID
throw redirect(301, `/publication/id/${id}`);
throw redirect(301, `${ROUTES.ID}/${id}`);
}
} else if (dTag) {
throw redirect(301, `/publication/d/${dTag}`);
throw redirect(301, `${ROUTES.D_TAG}/${dTag}`);
}
// If no query parameters, redirect to the start page or show publication feed\
// AI-TODO: Redirect to a "not found" page.
throw redirect(301, "/start");
// If no query parameters, redirect to the start page
throw redirect(301, ROUTES.START);
};

89
src/routes/publication/[type]/[identifier]/+layout.server.ts

@ -1,95 +1,12 @@ @@ -1,95 +1,12 @@
import { error } from "@sveltejs/kit";
import type { LayoutServerLoad } from "./$types";
import type { NDKEvent } from "@nostr-dev-kit/ndk";
import { getActiveRelaySetAsNDKRelaySet } from "../../../../lib/ndk.ts";
import { getMatchingTags } from "../../../../lib/utils/nostrUtils.ts";
import { naddrDecode, neventDecode } from "../../../../lib/utils.ts";
import type NDK from "@nostr-dev-kit/ndk";
// AI-TODO: Use `fetchEventWithFallback` from `nostrUtils.ts` to retrieve events in this file.
/**
* Fetches an event by hex ID
*/
async function fetchEventById(ndk: NDK, id: string): Promise<NDKEvent> {
try {
const event = await ndk.fetchEvent(id);
if (!event) {
throw new Error(`Event not found for ID: ${id}`);
}
return event;
} catch (err) {
throw error(404, `Failed to fetch publication root event.\n${err}`);
}
}
/**
* Fetches an event by d tag
*/
async function fetchEventByDTag(ndk: NDK, dTag: string): Promise<NDKEvent> {
try {
const relaySet = await getActiveRelaySetAsNDKRelaySet(ndk, true);
const events = await ndk.fetchEvents(
{ "#d": [dTag] },
{ closeOnEose: false },
relaySet,
);
if (!events || events.size === 0) {
throw new Error(`Event not found for d tag: ${dTag}`);
}
// Choose the event with the latest created_at timestamp when multiple events share the same d tag
const sortedEvents = Array.from(events).sort((a, b) => (b.created_at || 0) - (a.created_at || 0));
return sortedEvents[0];
} catch (err) {
throw error(404, `Failed to fetch publication root event.\n${err}`);
}
}
/**
* Fetches an event by naddr identifier
*/
async function fetchEventByNaddr(ndk: NDK, naddr: string): Promise<NDKEvent> {
try {
const decoded = naddrDecode(naddr);
const relaySet = await getActiveRelaySetAsNDKRelaySet(ndk, true);
const filter = {
kinds: [decoded.kind],
authors: [decoded.pubkey],
"#d": [decoded.identifier],
};
const event = await ndk.fetchEvent(filter, { closeOnEose: false }, relaySet);
if (!event) {
throw new Error(`Event not found for naddr: ${naddr}`);
}
return event;
} catch (err) {
throw error(404, `Failed to fetch publication root event.\n${err}`);
}
}
/**
* Fetches an event by nevent identifier
*/
async function fetchEventByNevent(ndk: NDK, nevent: string): Promise<NDKEvent> {
try {
const decoded = neventDecode(nevent);
const event = await ndk.fetchEvent(decoded.id);
if (!event) {
throw new Error(`Event not found for nevent: ${nevent}`);
}
return event;
} catch (err) {
throw error(404, `Failed to fetch publication root event.\n${err}`);
}
}
import { getMatchingTags, fetchEventById, fetchEventByDTag, fetchEventByNaddr, fetchEventByNevent } from "../../../../lib/utils/nostrUtils.ts";
export const load: LayoutServerLoad = async ({ params, parent, url }) => {
const { type, identifier } = params;
const { ndk } = await parent();
// deno-lint-ignore no-explicit-any
const { ndk } = (await parent()) as any;
if (!ndk) {
throw error(500, "NDK not available");

89
src/routes/publication/[type]/[identifier]/+page.server.ts

@ -1,95 +1,12 @@ @@ -1,95 +1,12 @@
import { error } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";
import type { NDKEvent } from "@nostr-dev-kit/ndk";
import { getActiveRelaySetAsNDKRelaySet } from "../../../../lib/ndk.ts";
import { getMatchingTags } from "../../../../lib/utils/nostrUtils.ts";
import { naddrDecode, neventDecode } from "../../../../lib/utils.ts";
import type NDK from "@nostr-dev-kit/ndk";
// AI-TODO: Use `fetchEventWithFallback` from `nostrUtils.ts` to retrieve events in this file.
/**
* Fetches an event by hex ID
*/
async function fetchEventById(ndk: NDK, id: string): Promise<NDKEvent> {
try {
const event = await ndk.fetchEvent(id);
if (!event) {
throw new Error(`Event not found for ID: ${id}`);
}
return event;
} catch (err) {
throw error(404, `Failed to fetch publication root event.\n${err}`);
}
}
/**
* Fetches an event by d tag
*/
async function fetchEventByDTag(ndk: NDK, dTag: string): Promise<NDKEvent> {
try {
const relaySet = await getActiveRelaySetAsNDKRelaySet(ndk, true); // true for inbox relays
const events = await ndk.fetchEvents(
{ "#d": [dTag] },
{ closeOnEose: false },
relaySet,
);
if (!events || events.size === 0) {
throw new Error(`Event not found for d tag: ${dTag}`);
}
// AI-NOTE: Choose the event with the latest created_at timestamp when multiple events share the same d tag
const sortedEvents = Array.from(events).sort((a, b) => (b.created_at || 0) - (a.created_at || 0));
return sortedEvents[0];
} catch (err) {
throw error(404, `Failed to fetch publication root event.\n${err}`);
}
}
/**
* Fetches an event by naddr identifier
*/
async function fetchEventByNaddr(ndk: NDK, naddr: string): Promise<NDKEvent> {
try {
const decoded = naddrDecode(naddr);
const relaySet = await getActiveRelaySetAsNDKRelaySet(ndk, true);
const filter = {
kinds: [decoded.kind],
authors: [decoded.pubkey],
"#d": [decoded.identifier],
};
const event = await ndk.fetchEvent(filter, { closeOnEose: false }, relaySet);
if (!event) {
throw new Error(`Event not found for naddr: ${naddr}`);
}
return event;
} catch (err) {
throw error(404, `Failed to fetch publication root event.\n${err}`);
}
}
/**
* Fetches an event by nevent identifier
*/
async function fetchEventByNevent(ndk: NDK, nevent: string): Promise<NDKEvent> {
try {
const decoded = neventDecode(nevent);
const event = await ndk.fetchEvent(decoded.id);
if (!event) {
throw new Error(`Event not found for nevent: ${nevent}`);
}
return event;
} catch (err) {
throw error(404, `Failed to fetch publication root event.\n${err}`);
}
}
import { getMatchingTags, fetchEventById, fetchEventByDTag, fetchEventByNaddr, fetchEventByNevent } from "../../../../lib/utils/nostrUtils.ts";
export const load: PageServerLoad = async ({ params, parent }) => {
const { type, identifier } = params;
const { ndk } = await parent();
// deno-lint-ignore no-explicit-any
const { ndk } = (await parent()) as any;
if (!ndk) {
throw error(500, "NDK not available");

Loading…
Cancel
Save