|
|
|
|
@ -71,127 +71,90 @@ func (ws *WikiService) FetchWikiIndex(ctx context.Context, naddrStr string) (*In
@@ -71,127 +71,90 @@ func (ws *WikiService) FetchWikiIndex(ctx context.Context, naddrStr string) (*In
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// FetchWikiEvents fetches all wiki events referenced in an index
|
|
|
|
|
// Queries by kind only, then filters locally and batch-fetches profiles
|
|
|
|
|
func (ws *WikiService) FetchWikiEvents(ctx context.Context, index *IndexEvent) ([]*WikiEvent, error) { |
|
|
|
|
var wikiEvents []*WikiEvent |
|
|
|
|
|
|
|
|
|
// Build a map of expected items (kind:pubkey:dtag) for fast lookup
|
|
|
|
|
expectedItems := make(map[string]IndexItem) |
|
|
|
|
for _, item := range index.Items { |
|
|
|
|
if item.Kind != ws.wikiKind { |
|
|
|
|
continue // Skip non-wiki items
|
|
|
|
|
if item.Kind == ws.wikiKind { |
|
|
|
|
key := fmt.Sprintf("%d:%s:%s", item.Kind, item.Pubkey, item.DTag) |
|
|
|
|
expectedItems[key] = item |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Create filter for this wiki event
|
|
|
|
|
filter := nostr.Filter{ |
|
|
|
|
Kinds: []int{ws.wikiKind}, |
|
|
|
|
Authors: []string{item.Pubkey}, |
|
|
|
|
Tags: map[string][]string{ |
|
|
|
|
"d": {item.DTag}, |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
if len(expectedItems) == 0 { |
|
|
|
|
return []*WikiEvent{}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// If event ID is specified, use it for more reliable fetching
|
|
|
|
|
if item.EventID != "" { |
|
|
|
|
filter.IDs = []string{item.EventID} |
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"event_id": item.EventID[:16] + "...", |
|
|
|
|
"dtag": item.DTag, |
|
|
|
|
}).Debug("Using event ID for wiki event") |
|
|
|
|
} |
|
|
|
|
// Query ALL events of this kind - simple query by kind only
|
|
|
|
|
filter := nostr.Filter{ |
|
|
|
|
Kinds: []int{ws.wikiKind}, |
|
|
|
|
Limit: 10000, // Reasonable limit
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
logFilter(filter, fmt.Sprintf("wiki event %s", item.DTag)) |
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"kind": ws.wikiKind, |
|
|
|
|
"items": len(expectedItems), |
|
|
|
|
}).Debug("Querying all events of kind from relays") |
|
|
|
|
|
|
|
|
|
// Use relay hint if available, otherwise use default client relays
|
|
|
|
|
var event *nostr.Event |
|
|
|
|
var err error |
|
|
|
|
if item.RelayHint != "" { |
|
|
|
|
// Connect to the relay hint
|
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"relay_hint": item.RelayHint, |
|
|
|
|
"dtag": item.DTag, |
|
|
|
|
}).Debug("Trying relay hint") |
|
|
|
|
relay, relayErr := ws.client.ConnectToRelay(ctx, item.RelayHint) |
|
|
|
|
if relayErr == nil { |
|
|
|
|
events, queryErr := relay.QuerySync(ctx, filter) |
|
|
|
|
relay.Close() |
|
|
|
|
if queryErr == nil { |
|
|
|
|
if len(events) > 0 { |
|
|
|
|
event = events[0] |
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"dtag": item.DTag, |
|
|
|
|
"relay_hint": item.RelayHint, |
|
|
|
|
}).Debug("Successfully fetched from relay hint") |
|
|
|
|
} else { |
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"relay_hint": item.RelayHint, |
|
|
|
|
"dtag": item.DTag, |
|
|
|
|
"has_event_id": item.EventID != "", |
|
|
|
|
}).Debug("Relay hint returned 0 events") |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"relay_hint": item.RelayHint, |
|
|
|
|
"dtag": item.DTag, |
|
|
|
|
}).Debugf("Error querying relay hint: %v", queryErr) |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"relay_hint": item.RelayHint, |
|
|
|
|
"dtag": item.DTag, |
|
|
|
|
}).Debugf("Error connecting to relay hint: %v", relayErr) |
|
|
|
|
// Fetch all events of this kind
|
|
|
|
|
allEvents, err := ws.client.FetchEvents(ctx, filter) |
|
|
|
|
if err != nil { |
|
|
|
|
logger.WithField("error", err).Warn("Failed to fetch events by kind") |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"fetched": len(allEvents), |
|
|
|
|
"expected": len(expectedItems), |
|
|
|
|
}).Debug("Fetched events, filtering locally") |
|
|
|
|
|
|
|
|
|
// Filter events locally by matching against index items
|
|
|
|
|
eventMap := make(map[string]*nostr.Event) // Map by kind:pubkey:dtag
|
|
|
|
|
for _, event := range allEvents { |
|
|
|
|
// Extract d-tag from event
|
|
|
|
|
var dTag string |
|
|
|
|
for _, tag := range event.Tags { |
|
|
|
|
if len(tag) > 0 && tag[0] == "d" && len(tag) > 1 { |
|
|
|
|
dTag = tag[1] |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Fallback to default client relays if relay hint failed or wasn't provided
|
|
|
|
|
// Client now queries all three relays automatically
|
|
|
|
|
if event == nil { |
|
|
|
|
logger.WithField("dtag", item.DTag).Debug("Querying all relays") |
|
|
|
|
event, err = ws.client.FetchEvent(ctx, filter) |
|
|
|
|
if err != nil { |
|
|
|
|
// If still not found and event ID was specified, try without event ID to get latest replaceable event
|
|
|
|
|
if item.EventID != "" { |
|
|
|
|
logger.WithField("dtag", item.DTag).Debug("Trying without event ID to get latest replaceable event") |
|
|
|
|
filterWithoutID := nostr.Filter{ |
|
|
|
|
Kinds: []int{ws.wikiKind}, |
|
|
|
|
Authors: []string{item.Pubkey}, |
|
|
|
|
Tags: map[string][]string{ |
|
|
|
|
"d": {item.DTag}, |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
logFilter(filterWithoutID, fmt.Sprintf("wiki event %s (without event ID)", item.DTag)) |
|
|
|
|
event, err = ws.client.FetchEvent(ctx, filterWithoutID) |
|
|
|
|
if err == nil { |
|
|
|
|
logger.WithField("dtag", item.DTag).Debug("Successfully fetched latest from all relays") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
logger.WithField("dtag", item.DTag).Debug("Successfully fetched from all relays") |
|
|
|
|
} |
|
|
|
|
if dTag == "" { |
|
|
|
|
continue // Skip events without d-tag
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if event == nil { |
|
|
|
|
// Log error but continue with other events
|
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"dtag": item.DTag, |
|
|
|
|
"pubkey": item.Pubkey, |
|
|
|
|
"has_event_id": item.EventID != "", |
|
|
|
|
}).Warnf("Error fetching wiki event: %v", err) |
|
|
|
|
continue |
|
|
|
|
key := fmt.Sprintf("%d:%s:%s", event.Kind, event.PubKey, dTag) |
|
|
|
|
if _, expected := expectedItems[key]; expected { |
|
|
|
|
// Keep the newest version if we have multiple
|
|
|
|
|
existing, exists := eventMap[key] |
|
|
|
|
if !exists || event.CreatedAt > existing.CreatedAt { |
|
|
|
|
eventMap[key] = event |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Parse the wiki event
|
|
|
|
|
// Convert matched events to wiki events
|
|
|
|
|
var wikiEvents []*WikiEvent |
|
|
|
|
for key, event := range eventMap { |
|
|
|
|
wiki, err := ParseWikiEvent(event, ws.wikiKind) |
|
|
|
|
if err != nil { |
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"dtag": item.DTag, |
|
|
|
|
"key": key, |
|
|
|
|
}).Warnf("Error parsing wiki event: %v", err) |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
wikiEvents = append(wikiEvents, wiki) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"matched": len(wikiEvents), |
|
|
|
|
"expected": len(expectedItems), |
|
|
|
|
}).Debug("Matched wiki events") |
|
|
|
|
|
|
|
|
|
if len(wikiEvents) == 0 && len(index.Items) > 0 { |
|
|
|
|
logger.WithField("items", len(index.Items)).Warn("No wiki events could be fetched from index items") |
|
|
|
|
// Return empty slice instead of error to allow landing page generation
|
|
|
|
|
logger.WithField("items", len(index.Items)).Warn("No wiki events matched from fetched events") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return wikiEvents, nil |
|
|
|
|
@ -204,6 +167,7 @@ func (ws *WikiService) GetBlogKind() int {
@@ -204,6 +167,7 @@ func (ws *WikiService) GetBlogKind() int {
|
|
|
|
|
|
|
|
|
|
// FetchIndexEvents fetches all events of a specific kind referenced in an index
|
|
|
|
|
// Only supports article kinds configured in the service
|
|
|
|
|
// Queries by kind only, then filters locally
|
|
|
|
|
func (ws *WikiService) FetchIndexEvents(ctx context.Context, index *IndexEvent, targetKind int) ([]*nostr.Event, error) { |
|
|
|
|
// Check if the target kind is in the allowed article kinds
|
|
|
|
|
allowed := false |
|
|
|
|
@ -217,124 +181,85 @@ func (ws *WikiService) FetchIndexEvents(ctx context.Context, index *IndexEvent,
@@ -217,124 +181,85 @@ func (ws *WikiService) FetchIndexEvents(ctx context.Context, index *IndexEvent,
|
|
|
|
|
return nil, fmt.Errorf("unsupported event kind: %d (only %v are supported)", targetKind, ws.articleKinds) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var events []*nostr.Event |
|
|
|
|
|
|
|
|
|
// Build a map of expected items (kind:pubkey:dtag) for fast lookup
|
|
|
|
|
expectedItems := make(map[string]IndexItem) |
|
|
|
|
for _, item := range index.Items { |
|
|
|
|
// Only process items of the target kind
|
|
|
|
|
if item.Kind != targetKind { |
|
|
|
|
continue // Skip items that don't match the target kind
|
|
|
|
|
if item.Kind == targetKind { |
|
|
|
|
key := fmt.Sprintf("%d:%s:%s", item.Kind, item.Pubkey, item.DTag) |
|
|
|
|
expectedItems[key] = item |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Create filter for this event
|
|
|
|
|
filter := nostr.Filter{ |
|
|
|
|
Kinds: []int{targetKind}, |
|
|
|
|
Authors: []string{item.Pubkey}, |
|
|
|
|
Tags: map[string][]string{ |
|
|
|
|
"d": {item.DTag}, |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
if len(expectedItems) == 0 { |
|
|
|
|
return []*nostr.Event{}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// If event ID is specified, use it for more reliable fetching
|
|
|
|
|
if item.EventID != "" { |
|
|
|
|
filter.IDs = []string{item.EventID} |
|
|
|
|
} |
|
|
|
|
// Query ALL events of this kind - simple query by kind only
|
|
|
|
|
filter := nostr.Filter{ |
|
|
|
|
Kinds: []int{targetKind}, |
|
|
|
|
Limit: 10000, // Reasonable limit
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"kind": targetKind, |
|
|
|
|
"items": len(expectedItems), |
|
|
|
|
}).Debug("Querying all events of kind from relays") |
|
|
|
|
|
|
|
|
|
logFilter(filter, fmt.Sprintf("index event kind %d %s", targetKind, item.DTag)) |
|
|
|
|
|
|
|
|
|
// Use relay hint if available, otherwise use default client relays
|
|
|
|
|
var event *nostr.Event |
|
|
|
|
var err error |
|
|
|
|
if item.RelayHint != "" { |
|
|
|
|
// Connect to the relay hint
|
|
|
|
|
relay, relayErr := ws.client.ConnectToRelay(ctx, item.RelayHint) |
|
|
|
|
if relayErr == nil { |
|
|
|
|
relayEvents, queryErr := relay.QuerySync(ctx, filter) |
|
|
|
|
relay.Close() |
|
|
|
|
if queryErr == nil { |
|
|
|
|
if len(relayEvents) > 0 { |
|
|
|
|
event = relayEvents[0] |
|
|
|
|
} else { |
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"relay_hint": item.RelayHint, |
|
|
|
|
"dtag": item.DTag, |
|
|
|
|
"kind": targetKind, |
|
|
|
|
"has_event_id": item.EventID != "", |
|
|
|
|
}).Debug("Relay hint returned 0 events") |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"relay_hint": item.RelayHint, |
|
|
|
|
"dtag": item.DTag, |
|
|
|
|
"kind": targetKind, |
|
|
|
|
}).Debugf("Error querying relay hint: %v", queryErr) |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"relay_hint": item.RelayHint, |
|
|
|
|
"dtag": item.DTag, |
|
|
|
|
"kind": targetKind, |
|
|
|
|
}).Debugf("Error connecting to relay hint: %v", relayErr) |
|
|
|
|
// Fetch all events of this kind
|
|
|
|
|
allEvents, err := ws.client.FetchEvents(ctx, filter) |
|
|
|
|
if err != nil { |
|
|
|
|
logger.WithField("error", err).Warn("Failed to fetch events by kind") |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"fetched": len(allEvents), |
|
|
|
|
"expected": len(expectedItems), |
|
|
|
|
}).Debug("Fetched events, filtering locally") |
|
|
|
|
|
|
|
|
|
// Filter events locally by matching against index items
|
|
|
|
|
eventMap := make(map[string]*nostr.Event) // Map by kind:pubkey:dtag
|
|
|
|
|
for _, event := range allEvents { |
|
|
|
|
// Extract d-tag from event
|
|
|
|
|
var dTag string |
|
|
|
|
for _, tag := range event.Tags { |
|
|
|
|
if len(tag) > 0 && tag[0] == "d" && len(tag) > 1 { |
|
|
|
|
dTag = tag[1] |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Fallback to default client relays if relay hint failed or wasn't provided
|
|
|
|
|
// Client now queries all three relays automatically
|
|
|
|
|
if event == nil { |
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"dtag": item.DTag, |
|
|
|
|
"kind": targetKind, |
|
|
|
|
}).Debug("Querying all relays") |
|
|
|
|
event, err = ws.client.FetchEvent(ctx, filter) |
|
|
|
|
if err != nil { |
|
|
|
|
// If still not found and event ID was specified, try without event ID to get latest replaceable event
|
|
|
|
|
if item.EventID != "" { |
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"dtag": item.DTag, |
|
|
|
|
"kind": targetKind, |
|
|
|
|
}).Debug("Trying without event ID to get latest replaceable event") |
|
|
|
|
filterWithoutID := nostr.Filter{ |
|
|
|
|
Kinds: []int{targetKind}, |
|
|
|
|
Authors: []string{item.Pubkey}, |
|
|
|
|
Tags: map[string][]string{ |
|
|
|
|
"d": {item.DTag}, |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
logFilter(filterWithoutID, fmt.Sprintf("index event kind %d %s (without event ID)", targetKind, item.DTag)) |
|
|
|
|
event, err = ws.client.FetchEvent(ctx, filterWithoutID) |
|
|
|
|
if err == nil { |
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"dtag": item.DTag, |
|
|
|
|
"kind": targetKind, |
|
|
|
|
}).Debug("Successfully fetched latest from all relays") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"dtag": item.DTag, |
|
|
|
|
"kind": targetKind, |
|
|
|
|
}).Debug("Successfully fetched from all relays") |
|
|
|
|
} |
|
|
|
|
if dTag == "" { |
|
|
|
|
continue // Skip events without d-tag
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if event == nil { |
|
|
|
|
// Log error but continue with other events
|
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"dtag": item.DTag, |
|
|
|
|
"kind": targetKind, |
|
|
|
|
"pubkey": item.Pubkey, |
|
|
|
|
"has_event_id": item.EventID != "", |
|
|
|
|
}).Warnf("Error fetching event: %v", err) |
|
|
|
|
continue |
|
|
|
|
key := fmt.Sprintf("%d:%s:%s", event.Kind, event.PubKey, dTag) |
|
|
|
|
if _, expected := expectedItems[key]; expected { |
|
|
|
|
// Keep the newest version if we have multiple
|
|
|
|
|
existing, exists := eventMap[key] |
|
|
|
|
if !exists || event.CreatedAt > existing.CreatedAt { |
|
|
|
|
eventMap[key] = event |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Convert to result slice
|
|
|
|
|
events := make([]*nostr.Event, 0, len(eventMap)) |
|
|
|
|
for _, event := range eventMap { |
|
|
|
|
events = append(events, event) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"matched": len(events), |
|
|
|
|
"expected": len(expectedItems), |
|
|
|
|
"kind": targetKind, |
|
|
|
|
}).Debug("Matched index events") |
|
|
|
|
|
|
|
|
|
if len(events) == 0 && len(index.Items) > 0 { |
|
|
|
|
logger.WithFields(map[string]interface{}{ |
|
|
|
|
"kind": targetKind, |
|
|
|
|
"items": len(index.Items), |
|
|
|
|
}).Warn("No events could be fetched from index items") |
|
|
|
|
}).Warn("No events matched from fetched events") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return events, nil |
|
|
|
|
|