From 31328eaa2602477b5ad7ab5f1429567082ed1780 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Wed, 4 Feb 2026 13:48:23 +0100 Subject: [PATCH] bug-fixes --- src/lib/components/content/GifPicker.svelte | 4 +- src/lib/services/cache/cache-manager.ts | 6 ++ src/lib/services/cache/deletion-tracker.ts | 68 +++++++++++++++++++++ src/lib/services/cache/event-cache.ts | 49 ++++++++++----- src/lib/services/cache/indexeddb-store.ts | 2 +- src/routes/cache/+page.svelte | 16 ++++- 6 files changed, 123 insertions(+), 22 deletions(-) create mode 100644 src/lib/services/cache/deletion-tracker.ts diff --git a/src/lib/components/content/GifPicker.svelte b/src/lib/components/content/GifPicker.svelte index c0a2942..75229ad 100644 --- a/src/lib/components/content/GifPicker.svelte +++ b/src/lib/components/content/GifPicker.svelte @@ -342,7 +342,7 @@ pendingUpload = { file, fileUrl }; showMetadataForm = true; - // Reset metadata form + // Reset metadata form and prefill image URL with uploaded file URL metadataForm = { title: '', summary: '', @@ -350,7 +350,7 @@ dim: '', blurhash: '', thumb: '', - image: '', + image: fileUrl, // Prefill with the uploaded GIF URL content: '' }; } catch (error) { diff --git a/src/lib/services/cache/cache-manager.ts b/src/lib/services/cache/cache-manager.ts index a9a5ab0..5d7a065 100644 --- a/src/lib/services/cache/cache-manager.ts +++ b/src/lib/services/cache/cache-manager.ts @@ -140,6 +140,12 @@ export async function getAllCachedEvents( await tx.done; + // Filter out events that have deletion requests + const { getDeletedEventIds } = await import('./deletion-tracker.js'); + const eventIds = events.map(e => e.id); + const deletedIds = await getDeletedEventIds(eventIds); + events = events.filter(e => !deletedIds.has(e.id)); + // Apply search filter if provided if (options.searchTerm) { const searchLower = options.searchTerm.toLowerCase(); diff --git a/src/lib/services/cache/deletion-tracker.ts b/src/lib/services/cache/deletion-tracker.ts new file mode 100644 index 0000000..7056f60 --- /dev/null +++ b/src/lib/services/cache/deletion-tracker.ts @@ -0,0 +1,68 @@ +/** + * Track deletion requests to prevent re-caching deleted events + * Uses cached kind 5 (deletion request) events from the cache + */ + +import { getDB } from './indexeddb-store.js'; +import { KIND } from '../../types/kind-lookup.js'; +import type { CachedEvent } from './event-cache.js'; + +/** + * Get all event IDs that have deletion requests + * Queries the cache for kind 5 events and extracts event IDs from their 'e' tags + */ +async function getDeletedEventIdsFromCache(): Promise> { + const deletedSet = new Set(); + try { + const db = await getDB(); + const tx = db.transaction('events', 'readonly'); + const index = tx.store.index('kind'); + + // Get all kind 5 (deletion request) events + for await (const cursor of index.iterate(KIND.EVENT_DELETION)) { + const deletionEvent = cursor.value as CachedEvent; + // Extract event IDs from 'e' tags + if (deletionEvent.tags) { + for (const tag of deletionEvent.tags) { + if (tag[0] === 'e' && tag[1]) { + deletedSet.add(tag[1]); + } + } + } + } + + await tx.done; + } catch (error) { + console.debug('Error getting deleted event IDs from cache:', error); + } + + return deletedSet; +} + +/** + * Check if an event is marked as deleted (has a deletion request in cache) + */ +export async function isEventDeleted(eventId: string): Promise { + try { + const deletedIds = await getDeletedEventIdsFromCache(); + return deletedIds.has(eventId); + } catch (error) { + console.debug('Error checking if event is deleted:', error); + return false; + } +} + +/** + * Check if any of the given event IDs are marked as deleted + * Returns a Set of deleted event IDs for efficient lookup + */ +export async function getDeletedEventIds(eventIds: string[]): Promise> { + try { + const deletedIds = await getDeletedEventIdsFromCache(); + // Return intersection of requested IDs and deleted IDs + return new Set(eventIds.filter(id => deletedIds.has(id))); + } catch (error) { + console.debug('Error checking deleted events:', error); + return new Set(); + } +} diff --git a/src/lib/services/cache/event-cache.ts b/src/lib/services/cache/event-cache.ts index f079a70..df79dfc 100644 --- a/src/lib/services/cache/event-cache.ts +++ b/src/lib/services/cache/event-cache.ts @@ -3,6 +3,7 @@ */ import { getDB } from './indexeddb-store.js'; +import { isEventDeleted, getDeletedEventIds } from './deletion-tracker.js'; import type { NostrEvent } from '../../types/nostr.js'; export interface CachedEvent extends NostrEvent { @@ -14,12 +15,17 @@ export interface CachedEvent extends NostrEvent { */ export async function cacheEvent(event: NostrEvent): Promise { try { - const db = await getDB(); - const cached: CachedEvent = { - ...event, - cached_at: Date.now() - }; - await db.put('events', cached); + // Don't cache events that have deletion requests + if (await isEventDeleted(event.id)) { + return; + } + + const db = await getDB(); + const cached: CachedEvent = { + ...event, + cached_at: Date.now() + }; + await db.put('events', cached); } catch (error) { console.debug('Error caching event:', error); // Don't throw - caching failures shouldn't break the app @@ -31,16 +37,27 @@ export async function cacheEvent(event: NostrEvent): Promise { */ export async function cacheEvents(events: NostrEvent[]): Promise { try { - const db = await getDB(); - const tx = db.transaction('events', 'readwrite'); - for (const event of events) { - const cached: CachedEvent = { - ...event, - cached_at: Date.now() - }; - await tx.store.put(cached); - } - await tx.done; + if (events.length === 0) return; + + // Check which events are marked as deleted + const eventIds = events.map(e => e.id); + const deletedIds = await getDeletedEventIds(eventIds); + + // Filter out deleted events + const eventsToCache = events.filter(e => !deletedIds.has(e.id)); + + if (eventsToCache.length === 0) return; + + const db = await getDB(); + const tx = db.transaction('events', 'readwrite'); + for (const event of eventsToCache) { + const cached: CachedEvent = { + ...event, + cached_at: Date.now() + }; + await tx.store.put(cached); + } + await tx.done; } catch (error) { console.debug('Error caching events:', error); // Don't throw - caching failures shouldn't break the app diff --git a/src/lib/services/cache/indexeddb-store.ts b/src/lib/services/cache/indexeddb-store.ts index 2f55d75..d99c665 100644 --- a/src/lib/services/cache/indexeddb-store.ts +++ b/src/lib/services/cache/indexeddb-store.ts @@ -5,7 +5,7 @@ import { openDB, type IDBPDatabase } from 'idb'; const DB_NAME = 'aitherboard'; -const DB_VERSION = 2; // Incremented to force upgrade when stores are missing +const DB_VERSION = 3; // Incremented to add deletion_requests store export interface DatabaseSchema { events: { diff --git a/src/routes/cache/+page.svelte b/src/routes/cache/+page.svelte index 03c7e87..43ffaea 100644 --- a/src/routes/cache/+page.svelte +++ b/src/routes/cache/+page.svelte @@ -3,11 +3,12 @@ import { onMount } from 'svelte'; import { goto } from '$app/navigation'; import { getCacheStats, getAllCachedEvents, clearAllCache, clearCacheByKind, clearCacheByKinds, clearCacheByDate, deleteEventById, type CacheStats } from '../../lib/services/cache/cache-manager.js'; + import { cacheEvent } from '../../lib/services/cache/event-cache.js'; import type { CachedEvent } from '../../lib/services/cache/event-cache.js'; import { KIND, getKindInfo } from '../../lib/types/kind-lookup.js'; import { nip19 } from 'nostr-tools'; import { sessionManager } from '../../lib/services/auth/session-manager.js'; - import { signAndPublish } from '../../lib/services/nostr/auth-handler.js'; + import { nostrClient } from '../../lib/services/nostr/nostr-client.js'; import type { NostrEvent } from '../../lib/types/nostr.js'; let stats = $state(null); @@ -352,9 +353,18 @@ content: '' }; - const result = await signAndPublish(deleteEvent); + // Sign the deletion request event first + const signedDeleteEvent = await sessionManager.signEvent(deleteEvent); + + // Cache the deletion request event so it prevents re-caching of the deleted event + // This must happen before publishing to ensure it's in cache + await cacheEvent(signedDeleteEvent); + + // Publish the already-signed event directly + // Note: nostrClient.publish() will also cache it, but that's fine (idempotent) + const result = await nostrClient.publish(signedDeleteEvent); if (result.success.length > 0) { - // Also delete from cache + // Also delete the original event from cache try { await deleteEventById(event.id); events = events.filter(e => e.id !== event.id);