Browse Source

refactor

master
Silberengel 4 weeks ago
parent
commit
07dbb55b55
  1. 28
      internal/config/config.go
  2. 18
      internal/nostr/client.go
  3. 53
      internal/nostr/ebooks.go
  4. 321
      internal/nostr/events.go
  5. 15
      internal/nostr/feed.go
  6. 27
      internal/nostr/issues.go
  7. 133
      internal/nostr/wiki.go
  8. 40
      internal/server/handlers.go

28
internal/config/config.go

@ -96,12 +96,12 @@ func LoadConfig(path string) (*Config, error) { @@ -96,12 +96,12 @@ func LoadConfig(path string) (*Config, error) {
return &config, nil
}
// GetProfilesRelays parses the comma-separated profiles relay string into a slice
func (c *Config) GetProfilesRelays() []string {
if c.Relays.Profiles == "" {
// parseRelayList parses a comma-separated relay string into a slice
func parseRelayList(relayStr string) []string {
if relayStr == "" {
return []string{}
}
relays := strings.Split(c.Relays.Profiles, ",")
relays := strings.Split(relayStr, ",")
result := make([]string, 0, len(relays))
for _, relay := range relays {
relay = strings.TrimSpace(relay)
@ -114,22 +114,14 @@ func (c *Config) GetProfilesRelays() []string { @@ -114,22 +114,14 @@ func (c *Config) GetProfilesRelays() []string {
return result
}
// GetProfilesRelays parses the comma-separated profiles relay string into a slice
func (c *Config) GetProfilesRelays() []string {
return parseRelayList(c.Relays.Profiles)
}
// GetContactFormRelays parses the comma-separated contact form relay string into a slice
func (c *Config) GetContactFormRelays() []string {
if c.Relays.ContactForm == "" {
return []string{}
}
relays := strings.Split(c.Relays.ContactForm, ",")
result := make([]string, 0, len(relays))
for _, relay := range relays {
relay = strings.TrimSpace(relay)
// Remove quotes if present
relay = strings.Trim(relay, "\"'")
if relay != "" {
result = append(result, relay)
}
}
return result
return parseRelayList(c.Relays.ContactForm)
}
// Validate validates the configuration

18
internal/nostr/client.go

@ -3,6 +3,7 @@ package nostr @@ -3,6 +3,7 @@ package nostr
import (
"context"
"fmt"
"sort"
"time"
"gitcitadel-online/internal/logger"
@ -284,12 +285,12 @@ func (c *Client) FetchDeletionEvents(ctx context.Context, authors []string) (map @@ -284,12 +285,12 @@ func (c *Client) FetchDeletionEvents(ctx context.Context, authors []string) (map
}
// Parse deletion events - extract event IDs from "e" tags
// According to NIP-09, "e" tags have format ["e", "event_id", ...]
// Only the first element (event_id) should be used, not additional elements
deletedEventIDs := make(map[string]*nostr.Event)
for _, deletionEvent := range deletionEvents {
// Kind 5 events have "e" tags with the event IDs they're deleting
for _, tag := range deletionEvent.Tags {
if len(tag) > 0 && tag[0] == "e" && len(tag) > 1 {
eventID := tag[1]
for _, eventID := range getETagValues(deletionEvent.Tags) {
// Keep the newest deletion event if multiple deletions exist
existing, exists := deletedEventIDs[eventID]
if !exists || deletionEvent.CreatedAt > existing.CreatedAt {
@ -297,7 +298,6 @@ func (c *Client) FetchDeletionEvents(ctx context.Context, authors []string) (map @@ -297,7 +298,6 @@ func (c *Client) FetchDeletionEvents(ctx context.Context, authors []string) (map
}
}
}
}
logger.WithFields(map[string]interface{}{
"deletion_events": len(deletionEvents),
@ -558,13 +558,9 @@ func (c *Client) ProcessEventsWithCache( @@ -558,13 +558,9 @@ func (c *Client) ProcessEventsWithCache(
allEvents = FilterDeletedEvents(allEvents, deletedEventIDs)
// Step 6: Sort newest-first (by created_at descending)
for i := 0; i < len(allEvents)-1; i++ {
for j := i + 1; j < len(allEvents); j++ {
if allEvents[i].CreatedAt < allEvents[j].CreatedAt {
allEvents[i], allEvents[j] = allEvents[j], allEvents[i]
}
}
}
sort.Slice(allEvents, func(i, j int) bool {
return allEvents[i].CreatedAt > allEvents[j].CreatedAt
})
logger.WithFields(map[string]interface{}{
"after_deletion": len(allEvents),

53
internal/nostr/ebooks.go

@ -108,57 +108,18 @@ func (es *EBooksService) FetchTopLevelIndexEvents(ctx context.Context) ([]EBookI @@ -108,57 +108,18 @@ func (es *EBooksService) FetchTopLevelIndexEvents(ctx context.Context) ([]EBookI
CreatedAt: int64(event.CreatedAt),
}
// Extract d tag
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "d" && len(tag) > 1 {
ebook.DTag = tag[1]
break
}
}
// Extract title
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "title" && len(tag) > 1 {
ebook.Title = tag[1]
break
}
}
// Extract common tags using utilities
ebook.DTag = getDTag(event.Tags)
ebook.Title = getTitle(event.Tags)
if ebook.Title == "" {
ebook.Title = ebook.DTag // Fallback to d tag
}
ebook.Summary = getSummary(event.Tags)
ebook.Type = getTagValue(event.Tags, "type")
ebook.Image = getImage(event.Tags)
// Extract summary
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "summary" && len(tag) > 1 {
ebook.Summary = tag[1]
break
}
}
// Extract type
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "type" && len(tag) > 1 {
ebook.Type = tag[1]
break
}
}
// Extract image
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "image" && len(tag) > 1 {
ebook.Image = tag[1]
break
}
}
// Build naddr - create proper bech32-encoded naddr
// Extract relay hints from event tags if available
var relays []string
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "relays" {
relays = append(relays, tag[1:]...)
}
}
relays := getAllTagValues(event.Tags, "relays")
// If no relays in tags, use the relay we fetched from
if len(relays) == 0 {
relays = []string{es.relayURL}

321
internal/nostr/events.go

@ -7,6 +7,97 @@ import ( @@ -7,6 +7,97 @@ import (
"github.com/nbd-wtf/go-nostr"
)
// Tag utilities for extracting common tags from events
// getTagValue extracts the first value for a given tag name
func getTagValue(tags nostr.Tags, tagName string) string {
for _, tag := range tags {
if len(tag) > 0 && tag[0] == tagName && len(tag) > 1 {
return tag[1]
}
}
return ""
}
// getAllTagValues extracts all values for a given tag name
// For tags that may have multiple values (like "relays", "maintainers")
func getAllTagValues(tags nostr.Tags, tagName string) []string {
var values []string
for _, tag := range tags {
if len(tag) > 0 && tag[0] == tagName && len(tag) > 1 {
values = append(values, tag[1:]...)
}
}
return values
}
// getETagValues extracts event IDs from "e" tags
// According to NIP-09, "e" tags have format ["e", "event_id", ...]
// Only the first element (event_id) should be extracted, not additional elements like relay URLs or markers
func getETagValues(tags nostr.Tags) []string {
var eventIDs []string
for _, tag := range tags {
if len(tag) > 0 && tag[0] == "e" && len(tag) > 1 {
// Only take the first element (event_id), ignore additional elements
eventIDs = append(eventIDs, tag[1])
}
}
return eventIDs
}
// getDTag extracts the d tag from an event
func getDTag(tags nostr.Tags) string {
return getTagValue(tags, "d")
}
// getTitle extracts the title tag from an event
func getTitle(tags nostr.Tags) string {
return getTagValue(tags, "title")
}
// getSummary extracts the summary tag from an event
func getSummary(tags nostr.Tags) string {
return getTagValue(tags, "summary")
}
// getImage extracts the image tag from an event
func getImage(tags nostr.Tags) string {
return getTagValue(tags, "image")
}
// ContentEvent represents a generic content event with common fields
type ContentEvent struct {
Event *nostr.Event
DTag string
Title string
Summary string
Content string
Image string
}
// parseContentEvent parses common content event fields (used by wiki, blog, longform)
func parseContentEvent(event *nostr.Event, expectedKind int) (*ContentEvent, error) {
if event.Kind != expectedKind {
return nil, fmt.Errorf("expected kind %d, got %d", expectedKind, event.Kind)
}
ce := &ContentEvent{
Event: event,
Content: event.Content,
DTag: getDTag(event.Tags),
Title: getTitle(event.Tags),
Summary: getSummary(event.Tags),
Image: getImage(event.Tags),
}
// Fallback title to d tag if not set
if ce.Title == "" {
ce.Title = ce.DTag
}
return ce, nil
}
// IndexEvent represents a kind 30040 publication index event (NKBIP-01)
type IndexEvent struct {
Event *nostr.Event
@ -39,24 +130,13 @@ func ParseIndexEvent(event *nostr.Event, expectedKind int) (*IndexEvent, error) @@ -39,24 +130,13 @@ func ParseIndexEvent(event *nostr.Event, expectedKind int) (*IndexEvent, error)
index := &IndexEvent{
Event: event,
Author: event.PubKey,
}
// Extract d tag
var dTag string
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "d" && len(tag) > 1 {
dTag = tag[1]
break
}
}
index.DTag = dTag
// Extract title
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "title" && len(tag) > 1 {
index.Title = tag[1]
break
}
DTag: getDTag(event.Tags),
Title: getTitle(event.Tags),
Summary: getSummary(event.Tags),
Image: getImage(event.Tags),
Type: getTagValue(event.Tags, "type"),
Version: getTagValue(event.Tags, "version"),
AutoUpdate: getTagValue(event.Tags, "auto-update"),
}
// Extract 'a' tags (index items)
@ -86,224 +166,49 @@ func ParseIndexEvent(event *nostr.Event, expectedKind int) (*IndexEvent, error) @@ -86,224 +166,49 @@ func ParseIndexEvent(event *nostr.Event, expectedKind int) (*IndexEvent, error)
}
}
// Extract auto-update tag
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "auto-update" && len(tag) > 1 {
index.AutoUpdate = tag[1]
break
}
}
// Extract type tag
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "type" && len(tag) > 1 {
index.Type = tag[1]
break
}
}
// Extract version tag
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "version" && len(tag) > 1 {
index.Version = tag[1]
break
}
}
// Extract summary tag
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "summary" && len(tag) > 1 {
index.Summary = tag[1]
break
}
}
// Extract image tag
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "image" && len(tag) > 1 {
index.Image = tag[1]
break
}
}
return index, nil
}
// WikiEvent represents a kind 30818 wiki event (NIP-54)
type WikiEvent struct {
Event *nostr.Event
DTag string
Title string
Summary string
Content string
Image string
*ContentEvent
}
// ParseWikiEvent parses a wiki event according to NIP-54
func ParseWikiEvent(event *nostr.Event, expectedKind int) (*WikiEvent, error) {
if event.Kind != expectedKind {
return nil, fmt.Errorf("expected kind %d, got %d", expectedKind, event.Kind)
ce, err := parseContentEvent(event, expectedKind)
if err != nil {
return nil, err
}
wiki := &WikiEvent{
Event: event,
Content: event.Content,
}
// Extract d tag (normalized identifier)
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "d" && len(tag) > 1 {
wiki.DTag = tag[1]
break
}
}
// Extract title tag (optional, falls back to d tag)
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "title" && len(tag) > 1 {
wiki.Title = tag[1]
break
}
}
if wiki.Title == "" {
wiki.Title = wiki.DTag
}
// Extract summary tag (optional)
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "summary" && len(tag) > 1 {
wiki.Summary = tag[1]
break
}
}
// Extract image tag (optional)
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "image" && len(tag) > 1 {
wiki.Image = tag[1]
break
}
}
return wiki, nil
return &WikiEvent{ContentEvent: ce}, nil
}
// BlogEvent represents a kind 30041 blog event
type BlogEvent struct {
Event *nostr.Event
DTag string
Title string
Summary string
Content string
Image string
*ContentEvent
}
// ParseBlogEvent parses a blog event
func ParseBlogEvent(event *nostr.Event, expectedKind int) (*BlogEvent, error) {
if event.Kind != expectedKind {
return nil, fmt.Errorf("expected kind %d, got %d", expectedKind, event.Kind)
}
blog := &BlogEvent{
Event: event,
Content: event.Content,
}
// Extract d tag (normalized identifier)
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "d" && len(tag) > 1 {
blog.DTag = tag[1]
break
}
}
// Extract title tag (optional, falls back to d tag)
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "title" && len(tag) > 1 {
blog.Title = tag[1]
break
ce, err := parseContentEvent(event, expectedKind)
if err != nil {
return nil, err
}
}
if blog.Title == "" {
blog.Title = blog.DTag
}
// Extract summary tag (optional)
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "summary" && len(tag) > 1 {
blog.Summary = tag[1]
break
}
}
// Extract image tag (optional)
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "image" && len(tag) > 1 {
blog.Image = tag[1]
break
}
}
return blog, nil
return &BlogEvent{ContentEvent: ce}, nil
}
// LongformEvent represents a kind 30023 longform article event
type LongformEvent struct {
Event *nostr.Event
DTag string
Title string
Summary string
Content string
Image string
*ContentEvent
}
// ParseLongformEvent parses a longform article event
func ParseLongformEvent(event *nostr.Event, expectedKind int) (*LongformEvent, error) {
if event.Kind != expectedKind {
return nil, fmt.Errorf("expected kind %d, got %d", expectedKind, event.Kind)
ce, err := parseContentEvent(event, expectedKind)
if err != nil {
return nil, err
}
longform := &LongformEvent{
Event: event,
Content: event.Content,
}
// Extract d tag (normalized identifier)
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "d" && len(tag) > 1 {
longform.DTag = tag[1]
break
}
}
// Extract title tag (optional, falls back to d tag)
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "title" && len(tag) > 1 {
longform.Title = tag[1]
break
}
}
if longform.Title == "" {
longform.Title = longform.DTag
}
// Extract summary tag (optional)
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "summary" && len(tag) > 1 {
longform.Summary = tag[1]
break
}
}
// Extract image tag (optional)
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "image" && len(tag) > 1 {
longform.Image = tag[1]
break
}
}
return longform, nil
return &LongformEvent{ContentEvent: ce}, nil
}
// NormalizeDTag normalizes a d tag according to NIP-54 rules

15
internal/nostr/feed.go

@ -49,18 +49,9 @@ func (fs *FeedService) FetchFeedItems(ctx context.Context, feedRelay string, max @@ -49,18 +49,9 @@ func (fs *FeedService) FetchFeedItems(ctx context.Context, feedRelay string, max
}
// Extract title, summary, and image tags
for _, tag := range event.Tags {
if len(tag) > 0 && len(tag) > 1 {
switch tag[0] {
case "title":
item.Title = tag[1]
case "summary":
item.Summary = tag[1]
case "image":
item.Image = tag[1]
}
}
}
item.Title = getTitle(event.Tags)
item.Summary = getSummary(event.Tags)
item.Image = getImage(event.Tags)
items = append(items, item)
}

27
internal/nostr/issues.go

@ -51,31 +51,16 @@ func ParseRepoAnnouncement(event *nostr.Event, expectedKind int) (*RepoAnnouncem @@ -51,31 +51,16 @@ func ParseRepoAnnouncement(event *nostr.Event, expectedKind int) (*RepoAnnouncem
}
// Extract d tag
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "d" && len(tag) > 1 {
repo.DTag = tag[1]
break
}
}
repo.DTag = getDTag(event.Tags)
// Validate that DTag is set
if repo.DTag == "" {
return nil, fmt.Errorf("repository announcement event missing d tag")
}
// Extract relays tag
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "relays" && len(tag) > 1 {
repo.Relays = append(repo.Relays, tag[1:]...)
}
}
// Extract maintainers tag
for _, tag := range event.Tags {
if len(tag) > 0 && tag[0] == "maintainers" && len(tag) > 1 {
repo.Maintainers = append(repo.Maintainers, tag[1:]...)
}
}
// Extract relays and maintainers tags
repo.Relays = getAllTagValues(event.Tags, "relays")
repo.Maintainers = getAllTagValues(event.Tags, "maintainers")
return repo, nil
}
@ -195,6 +180,8 @@ func (s *IssueService) PublishIssue(ctx context.Context, repoAnnouncement *RepoA @@ -195,6 +180,8 @@ func (s *IssueService) PublishIssue(ctx context.Context, repoAnnouncement *RepoA
}
err = relay.Publish(ctx, *event)
// Note: SimplePool manages connections, but we close here for explicit cleanup
// Closing after publish attempt ensures cleanup regardless of success/failure
relay.Close()
if err != nil {
lastErr = err
@ -253,6 +240,8 @@ func (s *IssueService) PublishSignedIssue(ctx context.Context, signedEvent *nost @@ -253,6 +240,8 @@ func (s *IssueService) PublishSignedIssue(ctx context.Context, signedEvent *nost
}
err = relay.Publish(ctx, *signedEvent)
// Note: SimplePool manages connections, but we close here for explicit cleanup
// Closing after publish attempt ensures cleanup regardless of success/failure
relay.Close()
if err != nil {
lastErr = err

133
internal/nostr/wiki.go

@ -76,25 +76,24 @@ func (ws *WikiService) FetchWikiIndex(ctx context.Context, naddrStr string) (*In @@ -76,25 +76,24 @@ func (ws *WikiService) FetchWikiIndex(ctx context.Context, naddrStr string) (*In
return index, nil
}
// FetchWikiEvents fetches all wiki events referenced in an index
// Uses ProcessEventsWithCache for the initial fetch, then filters by index items
func (ws *WikiService) FetchWikiEvents(ctx context.Context, index *IndexEvent) ([]*WikiEvent, error) {
// fetchIndexEventsByKind is a helper that fetches events of a specific kind from an index
// Returns a map of kind:pubkey:dtag -> event for matching
func (ws *WikiService) fetchIndexEventsByKind(ctx context.Context, index *IndexEvent, targetKind int) (map[string]*nostr.Event, error) {
// 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 {
if item.Kind == targetKind {
key := fmt.Sprintf("%d:%s:%s", item.Kind, item.Pubkey, item.DTag)
expectedItems[key] = item
}
}
if len(expectedItems) == 0 {
return []*WikiEvent{}, nil
return make(map[string]*nostr.Event), nil
}
// Use ProcessEventsWithCache to fetch events of this kind
// Use a high display limit (1000) to ensure we get all events referenced in the index
// This means we'll fetch 2000 events, which should be enough for most cases
displayLimit := 1000
primaryRelay := ws.client.GetPrimaryRelay()
if primaryRelay == "" {
@ -102,37 +101,21 @@ func (ws *WikiService) FetchWikiEvents(ctx context.Context, index *IndexEvent) ( @@ -102,37 +101,21 @@ func (ws *WikiService) FetchWikiEvents(ctx context.Context, index *IndexEvent) (
}
logger.WithFields(map[string]interface{}{
"kind": ws.wikiKind,
"kind": targetKind,
"items": len(expectedItems),
"index_event_id": index.Event.ID,
}).Debug("Fetching wiki events using ProcessEventsWithCache with index")
}).Debug("Fetching events using ProcessEventsWithCache with index")
// Use standard process with index event ID: fetch index, query only referenced events, merge cache, deduplicate, filter deletions, sort, limit, fetch profiles
result, err := ws.client.ProcessEventsWithCache(ctx, ws.wikiKind, displayLimit, make(map[string]*nostr.Event), primaryRelay, index.Event.ID, ws.indexKind)
// Use standard process with index event ID
result, err := ws.client.ProcessEventsWithCache(ctx, targetKind, displayLimit, make(map[string]*nostr.Event), primaryRelay, index.Event.ID, ws.indexKind)
if err != nil {
logger.WithField("error", err).Warn("Failed to fetch wiki events using ProcessEventsWithCache")
return nil, err
return nil, fmt.Errorf("failed to fetch events using ProcessEventsWithCache: %w", err)
}
allEvents := result.Events
logger.WithFields(map[string]interface{}{
"fetched": len(allEvents),
"expected": len(expectedItems),
}).Debug("Fetched wiki events using ProcessEventsWithCache with index")
// Build event map by kind:pubkey:dtag for matching
eventMap := make(map[string]*nostr.Event)
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
}
}
for _, event := range result.Events {
dTag := getDTag(event.Tags)
if dTag == "" {
continue // Skip events without d-tag
}
@ -145,6 +128,24 @@ func (ws *WikiService) FetchWikiEvents(ctx context.Context, index *IndexEvent) ( @@ -145,6 +128,24 @@ func (ws *WikiService) FetchWikiEvents(ctx context.Context, index *IndexEvent) (
}
}
logger.WithFields(map[string]interface{}{
"fetched": len(result.Events),
"matched": len(eventMap),
"expected": len(expectedItems),
"kind": targetKind,
}).Debug("Fetched and matched events from index")
return eventMap, nil
}
// FetchWikiEvents fetches all wiki events referenced in an index
// Uses ProcessEventsWithCache for the initial fetch, then filters by index items
func (ws *WikiService) FetchWikiEvents(ctx context.Context, index *IndexEvent) ([]*WikiEvent, error) {
eventMap, err := ws.fetchIndexEventsByKind(ctx, index, ws.wikiKind)
if err != nil {
return nil, err
}
// Convert matched events to wiki events, preserving order from index.Items
var wikiEvents []*WikiEvent
for _, item := range index.Items {
@ -166,11 +167,6 @@ func (ws *WikiService) FetchWikiEvents(ctx context.Context, index *IndexEvent) ( @@ -166,11 +167,6 @@ func (ws *WikiService) FetchWikiEvents(ctx context.Context, index *IndexEvent) (
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 matched from fetched events")
}
@ -218,72 +214,11 @@ func (ws *WikiService) FetchIndexEvents(ctx context.Context, index *IndexEvent, @@ -218,72 +214,11 @@ func (ws *WikiService) FetchIndexEvents(ctx context.Context, index *IndexEvent,
return nil, fmt.Errorf("unsupported event kind: %d (only %v are supported)", targetKind, ws.articleKinds)
}
// 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 == targetKind {
key := fmt.Sprintf("%d:%s:%s", item.Kind, item.Pubkey, item.DTag)
expectedItems[key] = item
}
}
if len(expectedItems) == 0 {
return []*nostr.Event{}, nil
}
// Use ProcessEventsWithCache to fetch events of this kind
// Use a high display limit (1000) to ensure we get all events referenced in the index
// This means we'll fetch 2000 events, which should be enough for most cases
displayLimit := 1000
primaryRelay := ws.client.GetPrimaryRelay()
if primaryRelay == "" {
return nil, fmt.Errorf("primary relay not configured")
}
logger.WithFields(map[string]interface{}{
"kind": targetKind,
"items": len(expectedItems),
"index_event_id": index.Event.ID,
}).Debug("Fetching events using ProcessEventsWithCache with index")
// Use standard process with index event ID: fetch index, query only referenced events, merge cache, deduplicate, filter deletions, sort, limit, fetch profiles
result, err := ws.client.ProcessEventsWithCache(ctx, targetKind, displayLimit, make(map[string]*nostr.Event), primaryRelay, index.Event.ID, ws.indexKind)
eventMap, err := ws.fetchIndexEventsByKind(ctx, index, targetKind)
if err != nil {
logger.WithField("error", err).Warn("Failed to fetch events using ProcessEventsWithCache")
return nil, err
}
allEvents := result.Events
logger.WithFields(map[string]interface{}{
"fetched": len(allEvents),
"expected": len(expectedItems),
}).Debug("Fetched events using ProcessEventsWithCache with index")
// Build event map by kind:pubkey:dtag for matching
eventMap := make(map[string]*nostr.Event)
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
}
}
if dTag == "" {
continue // Skip events without d-tag
}
key := fmt.Sprintf("%d:%s:%s", event.Kind, event.PubKey, dTag)
// 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, preserving order from index.Items
events := make([]*nostr.Event, 0, len(eventMap))
for _, item := range index.Items {
@ -298,12 +233,6 @@ func (ws *WikiService) FetchIndexEvents(ctx context.Context, index *IndexEvent, @@ -298,12 +233,6 @@ func (ws *WikiService) FetchIndexEvents(ctx context.Context, index *IndexEvent,
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,

40
internal/server/handlers.go

@ -90,48 +90,36 @@ func (s *Server) handleWiki(w http.ResponseWriter, r *http.Request) { @@ -90,48 +90,36 @@ func (s *Server) handleWiki(w http.ResponseWriter, r *http.Request) {
s.servePage(w, r, page)
}
// handleBlog handles the blog page
func (s *Server) handleBlog(w http.ResponseWriter, r *http.Request) {
page, exists := s.cache.Get("/blog")
// handleCachedPage handles pages that are served from cache
func (s *Server) handleCachedPage(path string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
page, exists := s.cache.Get(path)
if !exists {
http.Error(w, "Page not ready", http.StatusServiceUnavailable)
return
}
s.servePage(w, r, page)
}
}
// handleArticles handles the articles page
func (s *Server) handleArticles(w http.ResponseWriter, r *http.Request) {
page, exists := s.cache.Get("/articles")
if !exists {
http.Error(w, "Page not ready", http.StatusServiceUnavailable)
return
// handleBlog handles the blog page
func (s *Server) handleBlog(w http.ResponseWriter, r *http.Request) {
s.handleCachedPage("/blog")(w, r)
}
s.servePage(w, r, page)
// handleArticles handles the articles page
func (s *Server) handleArticles(w http.ResponseWriter, r *http.Request) {
s.handleCachedPage("/articles")(w, r)
}
// handleEBooks handles the e-books listing page
func (s *Server) handleEBooks(w http.ResponseWriter, r *http.Request) {
page, exists := s.cache.Get("/ebooks")
if !exists {
http.Error(w, "Page not ready", http.StatusServiceUnavailable)
return
}
s.servePage(w, r, page)
s.handleCachedPage("/ebooks")(w, r)
}
// handleFeed handles the Feed page
func (s *Server) handleFeed(w http.ResponseWriter, r *http.Request) {
page, exists := s.cache.Get("/feed")
if !exists {
http.Error(w, "Page not ready", http.StatusServiceUnavailable)
return
}
s.servePage(w, r, page)
s.handleCachedPage("/feed")(w, r)
}
// handleContact handles the contact form (GET and POST)
@ -345,8 +333,8 @@ func (s *Server) handleContactAPI(w http.ResponseWriter, r *http.Request) { @@ -345,8 +333,8 @@ func (s *Server) handleContactAPI(w http.ResponseWriter, r *http.Request) {
}
err = relay.Publish(ctx, *req.Event)
// Note: SimplePool manages connections, but we close here for explicit cleanup
relay.Close()
if err != nil {
logger.WithFields(map[string]interface{}{
"relay": relayURL,

Loading…
Cancel
Save