From 32c484178fb82e4c277c1010f49dcbc8494bc460 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Wed, 18 Oct 2023 11:58:09 -0300 Subject: [PATCH] rewrite things to use htmlgo (wip). --- go.mod | 3 + go.sum | 5 +- handler.go | 269 ++++++++++++++++++++------------------------------ main.go | 9 +- pages.go | 92 +++++++++++++++++ ui/index.html | 32 ------ utils.go | 28 +----- whitelist.go | 8 +- 8 files changed, 219 insertions(+), 227 deletions(-) create mode 100644 pages.go delete mode 100644 ui/index.html diff --git a/go.mod b/go.mod index 5aaa6a3..c0f4e1e 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/kelseyhightower/envconfig v1.4.0 github.com/nbd-wtf/go-nostr v0.20.0 github.com/rs/zerolog v1.31.0 + github.com/theplant/htmlgo v1.0.3 ) require ( @@ -52,3 +53,5 @@ require ( golang.org/x/sys v0.12.0 // indirect google.golang.org/protobuf v1.23.0 // indirect ) + +replace github.com/fiatjaf/khatru => /home/fiatjaf/comp/khatru diff --git a/go.sum b/go.sum index a11c682..c83dd42 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,6 @@ github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/fasthttp/websocket v1.5.3 h1:TPpQuLwJYfd4LJPXvHDYPMFWbLjsT91n3GpWtCQtdek= github.com/fasthttp/websocket v1.5.3/go.mod h1:46gg/UBmTU1kUaTcwQXpUxtRwG2PvIZYeA8oL6vF3Fs= -github.com/fiatjaf/khatru v0.0.0-20231003113207-bbe186494e68 h1:oBx0uzT+KILepoPAPsjAghqAPSo9uV4pR1myr1rQQGA= -github.com/fiatjaf/khatru v0.0.0-20231003113207-bbe186494e68/go.mod h1:8shKDuVtrdLfsuHV4FBC3qYTTXnyfOLAgKqUt4u+Okk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= @@ -141,6 +139,9 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/theplant/htmlgo v1.0.3 h1:G7/YSf8OrOIRHVQ13avd78T/GV1kDl/jMwpQURrXB0o= +github.com/theplant/htmlgo v1.0.3/go.mod h1:pCKSFJsoVNkyW+yN2i1Mst+8130NSQzIU7L2IbnuyKg= +github.com/theplant/testingutils v0.0.0-20190603093022-26d8b4d95c61 h1:757/ruZNgTsOf5EkQBo0i3Bx/P2wgF5ljVkODeUX/uA= github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= diff --git a/handler.go b/handler.go index 037bbde..e9a78a1 100644 --- a/handler.go +++ b/handler.go @@ -3,12 +3,10 @@ package main import ( "context" "embed" - "fmt" - "html/template" "net/http" "strings" - "github.com/nbd-wtf/go-nostr" + "github.com/theplant/htmlgo" ) // embed ui files @@ -17,177 +15,126 @@ import ( var dist embed.FS func inviteTreeHandler(w http.ResponseWriter, r *http.Request) { - formattedInviteData := buildHTMLTree(whitelist, "") - - data := map[string]interface{}{ - "Relayname": s.RelayName, - "Relaydescription": s.RelayDescription, - "Pagetitle": "Invite Hierarchy", - "Pagecontent": ` - - ` + formattedInviteData, - } - - tmpl, err := template.ParseFS(dist, "ui/dist/index.html") - if err != nil { - http.Error(w, "Error parsing template: "+err.Error(), http.StatusInternalServerError) - return - } - - err = tmpl.Execute(w, data) - if err != nil { - http.Error(w, "Error executing template: "+err.Error(), http.StatusInternalServerError) - return - } + content := inviteTreePageHTML(r.Context(), InviteTreePageParams{}) + htmlgo.Fprint(w, baseHTML(content), r.Context()) } func reportsViewerHandler(w http.ResponseWriter, r *http.Request) { - var formattedReportsData template.HTML = "" + // var formattedReportsData template.HTML = "" - events, _ := db.QueryEvents(context.Background(), nostr.Filter{ - Kinds: []int{1984}, - Limit: 52, - }) + // 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 - } + // 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"}) + // 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), - )) - } - } - } + // 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, - } + // 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 - } + // 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 - } + // // 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 + // } } func homePageHandler(w http.ResponseWriter, r *http.Request) { - relayOwnerInfo := getUserInfo(context.Background(), s.RelayPubkey) - - data := map[string]interface{}{ - "Relayname": s.RelayName, - "Relaydescription": s.RelayDescription, - "Pagetitle": "Info", - "Pagecontent": template.HTML(fmt.Sprintf(` -
Relay Name: %v
-
Relay Description: %v
-
Relay Owner: %v
-
Relay Alternative Contact: %v
-
-
This relay uses Khatru Invite, which is build with Khatru
- `, s.RelayName, s.RelayDescription, relayOwnerInfo.Npub, relayOwnerInfo.Name, s.RelayContact)), - } - - tmpl, err := template.ParseFS(dist, "ui/dist/index.html") - if err != nil { - http.Error(w, "Error parsing template: "+err.Error(), http.StatusInternalServerError) - return - } - - err = tmpl.Execute(w, data) - if err != nil { - http.Error(w, "Error executing template: "+err.Error(), http.StatusInternalServerError) - return - } -} - -func redirectHandler(w http.ResponseWriter, r *http.Request) { - if strings.HasPrefix(r.URL.Path, "/assets") { - staticHandler("ui/dist", w, r) - } else if r.URL.Path == "/" { - homePageHandler(w, r) - } + content := homePageHTML(r.Context(), HomePageParams{ + RelayOwnerInfo: getUserInfo(context.Background(), s.RelayPubkey), + }) + htmlgo.Fprint(w, baseHTML(content), r.Context()) } func staticHandler(prefix string, w http.ResponseWriter, r *http.Request) { diff --git a/main.go b/main.go index a4bd461..8e20e08 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( ) type Settings struct { + Port string `envconfig:"PORT" default:"3334"` RelayName string `envconfig:"RELAY_NAME" required:"true"` RelayPubkey string `envconfig:"RELAY_PUBKEY" required:"true"` RelayDescription string `envconfig:"RELAY_DESCRIPTION"` @@ -83,10 +84,12 @@ func main() { // ui relay.Router().HandleFunc("/reports", reportsViewerHandler) relay.Router().HandleFunc("/users", inviteTreeHandler) - relay.Router().HandleFunc("/", redirectHandler) + relay.Router().HandleFunc("/", homePageHandler) - log.Info().Msg("running on http://127.0.0.1:3334") - http.ListenAndServe(":3334", relay) + log.Info().Msg("running on http://0.0.0.0:" + s.Port) + if err := http.ListenAndServe(":"+s.Port, relay); err != nil { + log.Fatal().Err(err).Msg("failed to serve") + } } // save whitelist on shutdown diff --git a/pages.go b/pages.go new file mode 100644 index 0000000..cb04c81 --- /dev/null +++ b/pages.go @@ -0,0 +1,92 @@ +package main + +import ( + "context" + + . "github.com/theplant/htmlgo" +) + +func baseHTML(inside HTMLComponent) HTMLComponent { + navItemClass := "text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 font-medium" + + return HTML( + Head( + Meta().Charset("utf-8"), + Meta().Name("viewport").Content("width=device-width, initial-scale=1"), + Title(s.RelayName), + Script("").Src("https://cdn.tailwindcss.com"), + ), + Body( + Div( + H1(s.RelayName).Class("font-bold text-2xl"), + P().Text(s.RelayDescription).Class("text-lg"), + ).Class("mx-auto my-6 text-center"), + Nav( + A().Text("information").Href("/").Class(navItemClass), + A().Text("invite tree").Href("/users").Class(navItemClass), + A().Text("reports").Href("/reports").Class(navItemClass), + ).Class("flex flex-1 items-center justify-center"), + Div(inside).Class("m-4"), + ).Class("bg-gray-800 mx-4 my-6 text-white"), + ) +} + +type HomePageParams struct { + RelayOwnerInfo SimpleUserInfo +} + +func homePageHTML(ctx context.Context, params HomePageParams) HTMLComponent { + contact := Div() + if s.RelayContact != "" { + contact = Div().Text("alternative contact: " + s.RelayContact) + } + + description := Div() + if s.RelayDescription != "" { + description = Div().Text("description: " + s.RelayDescription) + } + + return Div( + Div().Text("name: "+s.RelayName), + description, + contact, + Div( + Text("relay master: "), + A().Text(params.RelayOwnerInfo.Name).Href("nostr:"+params.RelayOwnerInfo.Npub), + ), + Br(), + Div( + Text("this relay uses"), + A().Target("_blank").Href("https://github.com/github-tijlxyz/khatru-invite").Text("Khatru Invite"), + Text(" which is built with "), + A().Target("_blank").Href("https://github.com/fiatjaf/khatru").Text("Khatru"), + ), + ) +} + +type InviteTreePageParams struct{} + +func inviteTreePageHTML(ctx context.Context, params InviteTreePageParams) HTMLComponent { + return Div( + Input("").Type("text").Placeholder("npub1..."), + Button("invite"), + buildInviteTree(ctx, ""), + ) +} + +func buildInviteTree(ctx context.Context, invitedBy string) HTMLComponent { + tree := Ul() + for _, entry := range whitelist { + if entry.InvitedBy == invitedBy { + user := getUserInfo(ctx, entry.PublicKey) + tree = tree.Children( + Li( + A().Href("nostr:"+user.Npub).Text(user.Name), + A().Text("remove"), + buildInviteTree(ctx, entry.PublicKey), + ), + ) + } + } + return tree +} diff --git a/ui/index.html b/ui/index.html deleted file mode 100644 index c3cfd70..0000000 --- a/ui/index.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - {{.Relayname}} | {{.Pagetitle}} - - - -
-

{{.Relayname}}

-

{{.Relaydescription}}

-
-
-
- - information - - invite tree - - admin reports viewer - -
-
-
-
- {{.Pagecontent}} -
- - - \ No newline at end of file diff --git a/utils.go b/utils.go index 2326e33..8c31ee5 100644 --- a/utils.go +++ b/utils.go @@ -3,37 +3,17 @@ package main import ( "context" "encoding/json" - "fmt" - "html/template" "github.com/nbd-wtf/go-nostr" ) -func buildHTMLTree(entries []WhitelistEntry, invitedBy string) template.HTML { - html := "" - return template.HTML(html) -} - -func isPkInWhitelist(targetPk string) bool { for i := 0; i < len(whitelist); i++ { - if whitelist[i].PublicKey == targetPk { + if whitelist[i].PublicKey == pubkey { return true } } diff --git a/whitelist.go b/whitelist.go index adb00a8..cb31bf9 100644 --- a/whitelist.go +++ b/whitelist.go @@ -17,7 +17,7 @@ var whitelist []WhitelistEntry func whitelistRejecter(ctx context.Context, evt *nostr.Event) (reject bool, msg string) { // check if user in whitelist - if !isPkInWhitelist(evt.PubKey) { + if !isPublicKeyInWhitelist(evt.PubKey) { return true, "You are not invited to this relay" } @@ -28,10 +28,8 @@ func whitelistRejecter(ctx context.Context, evt *nostr.Event) (reject bool, msg if evt.Kind == 20201 { pTags := evt.Tags.GetAll([]string{"p"}) for _, tag := range pTags { - if !isPkInWhitelist(tag.Value()) { - if nostr.IsValidPublicKeyHex(tag.Value()) { - whitelist = append(whitelist, WhitelistEntry{PublicKey: tag.Value(), InvitedBy: evt.PubKey}) - } + if nostr.IsValidPublicKeyHex(tag.Value()) && !isPublicKeyInWhitelist(tag.Value()) { + whitelist = append(whitelist, WhitelistEntry{PublicKey: tag.Value(), InvitedBy: evt.PubKey}) } } }