diff --git a/components.go b/components.go index 31505e1..3e9260e 100644 --- a/components.go +++ b/components.go @@ -32,13 +32,13 @@ func userRowComponent(ctx context.Context, profile sdk.ProfileMetadata, loggedUs } return Li( - userNameComponent(ctx, profile), + userNameComponent(profile), button, inviteTreeComponent(ctx, profile.PubKey, loggedUser), ).Class("ml-6") } -func userNameComponent(ctx context.Context, profile sdk.ProfileMetadata) HTMLComponent { +func userNameComponent(profile sdk.ProfileMetadata) HTMLComponent { return A().Href("nostr:" + profile.Npub()).Children( Span(profile.ShortName()).Attr( "npub", profile.Npub(), diff --git a/handler.go b/handler.go index acdbe11..929b03b 100644 --- a/handler.go +++ b/handler.go @@ -3,6 +3,7 @@ package main import ( "net/http" + "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip19" "github.com/theplant/htmlgo" ) @@ -43,114 +44,20 @@ func removeFromWhitelistHandler(w http.ResponseWriter, r *http.Request) { } func reportsViewerHandler(w http.ResponseWriter, r *http.Request) { - // var formattedReportsData template.HTML = "" + events, err := db.QueryEvents(r.Context(), nostr.Filter{ + Kinds: []int{1984}, + Limit: 52, + }) + if err != nil { + http.Error(w, "failed to query reports: "+err.Error(), 500) + return + } - // events, _ := db.QueryEvents(context.Background(), nostr.Filter{ - // Kinds: []int{1984}, - // Limit: 52, - // }) - - // type Report struct { - // ID string - // ByUser string - // AboutUser string - // AboutEvent string - // Type string - // Content string - // } - - // for ev := range events { - // pTag := ev.Tags.GetFirst([]string{"p"}) - - // eTag := ev.Tags.GetFirst([]string{"e"}) - // if pTag != nil { - // typeReport := eTag.Relay()[6:] - // if typeReport == "" { - // typeReport = pTag.Relay()[6:] - // } - // report := Report{ - // ID: ev.ID, - // ByUser: ev.PubKey, - // AboutUser: pTag.Value(), - // AboutEvent: eTag.Value(), - // Type: typeReport, - // Content: ev.Content, - // } - // // get AboutEvent content, note1 ect - // formattedReportsData += template.HTML(fmt.Sprintf(` - //
- //

Report %v

- //

By User: %v

- //

About User: %v

`, - // report.ID, - // getUserInfo(context.Background(), report.ByUser).Npub, - // getUserInfo(context.Background(), report.ByUser).Name, - // getUserInfo(context.Background(), report.AboutUser).Npub, - // getUserInfo(context.Background(), report.AboutUser).Name, - // )) - // if report.AboutEvent != "" { - // // fetch event data - // aboutEvents, _ := db.QueryEvents(context.TODO(), nostr.Filter{ - // IDs: []string{report.AboutEvent}, - // }) - // for aboutEvent := range aboutEvents { - // formattedReportsData += template.HTML(fmt.Sprintf(` - //

- // About Event:

- //

`, - // template.HTMLEscaper(aboutEvent.Kind), - // template.HTMLEscaper(aboutEvent.Tags), - // template.HTMLEscaper(aboutEvent.Content), - // )) - // } - // } - // formattedReportsData += template.HTML(fmt.Sprintf(` - //

Type: %v

`, - // report.Type, - // )) - // if report.Content != "" { - // formattedReportsData += template.HTML(fmt.Sprintf(` - //

Content: %v

- //
- // - // - // - //
- //
- //
`, - // template.HTMLEscaper(report.Content), - // template.HTMLEscaper(report.ID), - // template.HTMLEscaper(report.AboutUser), - // template.HTMLEscaper(report.ID), - // template.HTMLEscaper(report.ByUser), - // )) - // } - // } - // } - - // data := map[string]interface{}{ - // "Relayname": s.RelayName, - // "Relaydescription": s.RelayDescription, - // "Pagetitle": "Reports Viewer", - // "Pagecontent": formattedReportsData, - // } - - // tmpl, err := template.ParseFS(dist, "ui/dist/index.html") - // if err != nil { - // http.Error(w, "Error parsing template: "+err.Error(), http.StatusInternalServerError) - // return - // } - - // // Execute the template with the provided data and write it to the response - // err = tmpl.Execute(w, data) - // if err != nil { - // http.Error(w, "Error executing template: "+err.Error(), http.StatusInternalServerError) - // return - // } + content := reportsPageHTML(r.Context(), ReportsPageParams{ + reports: events, + loggedUser: getLoggedUser(r), + }) + htmlgo.Fprint(w, content, r.Context()) } func homePageHandler(w http.ResponseWriter, r *http.Request) { diff --git a/main.go b/main.go index bcad25c..d5ee953 100644 --- a/main.go +++ b/main.go @@ -56,7 +56,11 @@ func main() { relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents) relay.CountEvents = append(relay.CountEvents, db.CountEvents) relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent) - relay.RejectEvent = append(relay.RejectEvent, rejectEventsFromUsersNotInWhitelist) + relay.RejectEvent = append(relay.RejectEvent, + rejectEventsFromUsersNotInWhitelist, + restrictToKinds, + validateAndFilterReports, + ) // load users registry if err := loadWhitelist(); err != nil { diff --git a/pages.go b/pages.go index b050495..c3d47b2 100644 --- a/pages.go +++ b/pages.go @@ -3,6 +3,7 @@ package main import ( "context" + "github.com/nbd-wtf/go-nostr" sdk "github.com/nbd-wtf/nostr-sdk" . "github.com/theplant/htmlgo" ) @@ -62,7 +63,7 @@ func homePageHTML(ctx context.Context, params HomePageParams) HTMLComponent { contact, Div( Text("relay master: "), - userNameComponent(ctx, params.relayOwnerInfo), + userNameComponent(params.relayOwnerInfo), ), Br(), Div( @@ -102,3 +103,71 @@ func inviteTreePageHTML(ctx context.Context, params InviteTreePageParams) HTMLCo ).Id("tree").Class("mt-3"), ) } + +type ReportsPageParams struct { + reports chan *nostr.Event + loggedUser string +} + +func reportsPageHTML(ctx context.Context, params ReportsPageParams) HTMLComponent { + items := make([]HTMLComponent, 0, 52) + for report := range params.reports { + var primaryType string + var secondaryType string + var relatedContent HTMLComponent + + if e := report.Tags.GetFirst([]string{"e", ""}); e != nil { + // event report + res, _ := sys.StoreRelay().QuerySync(ctx, nostr.Filter{IDs: []string{(*e)[1]}}) + if len(res) == 0 { + sys.Store.DeleteEvent(ctx, report) + continue + } + + if len(*e) >= 3 { + primaryType = (*e)[2] + } + + relatedEvent := res[0] + relatedContent = Div( + Text("event reported: "), + Div().Text(relatedEvent.String()).Class("text-mono"), + ) + } else if p := report.Tags.GetFirst([]string{"p", ""}); p != nil { + // pubkey report + if !isPublicKeyInWhitelist((*p)[1]) { + sys.Store.DeleteEvent(ctx, report) + continue + } + + if len(*p) >= 3 { + primaryType = (*p)[2] + } + + relatedProfile := fetchAndStoreProfile(ctx, (*p)[1]) + relatedContent = Div( + Text("profile reported: "), + userNameComponent(relatedProfile), + ) + } else { + continue + } + + reporter := sys.FetchProfileMetadata(ctx, report.PubKey) + report := Div( + Div(Span(primaryType).Class("font-semibold"), Text(" report")).Class("font-lg"), + Div().Text(secondaryType), + Div(Text("by "), userNameComponent(reporter)), + Div().Text(report.Content).Class("p-3"), + relatedContent, + ) + + items = append(items, report) + } + return baseHTML( + Div( + H1("reports received").Class("text-xl p-4"), + Div(items...), + ), + ) +} diff --git a/relay.go b/relay.go index cbd158e..cfc3ee1 100644 --- a/relay.go +++ b/relay.go @@ -3,6 +3,7 @@ package main import ( "context" + "github.com/fiatjaf/khatru/plugins" "github.com/nbd-wtf/go-nostr" ) @@ -10,5 +11,33 @@ func rejectEventsFromUsersNotInWhitelist(ctx context.Context, event *nostr.Event if isPublicKeyInWhitelist(event.PubKey) { return false, "" } + if event.Kind == 1985 { + // we accept reports from anyone (will filter them for relevance in the next function) + return false, "" + } return true, "not authorized" } + +var restrictToKinds = plugins.RestrictToSpecifiedKinds( + 0, 1, 3, 5, 6, 8, 16, 1063, 1985, 9735, 10000, 10001, 10002, 30008, 30009, 30311, 31922, 31923, 31924, 31925) + +func validateAndFilterReports(ctx context.Context, event *nostr.Event) (reject bool, msg string) { + if event.Kind == 1985 { + if e := event.Tags.GetFirst([]string{"e", ""}); e != nil { + // event report: check if the target event is here + res, _ := sys.StoreRelay().QuerySync(ctx, nostr.Filter{IDs: []string{(*e)[1]}}) + if len(res) == 0 { + return true, "we don't know anything about the target event" + } + } else if p := event.Tags.GetFirst([]string{"p", ""}); p != nil { + // pubkey report + if !isPublicKeyInWhitelist((*p)[1]) { + return true, "target pubkey is not a user of this relay" + } + } else { + return true, "invalid report" + } + } + + return false, "" +}