rewrite things to use htmlgo (wip).

This commit is contained in:
fiatjaf 2023-10-18 11:58:09 -03:00
parent 6fbde5f302
commit 32c484178f
No known key found for this signature in database
GPG Key ID: BAD43C4BE5C1A3A1
8 changed files with 219 additions and 227 deletions

3
go.mod
View File

@ -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

5
go.sum
View File

@ -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=

View File

@ -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": `
<input type="text" id="inviteuser-input" placeholder="npub1..." /><button class="inviteuser">Invite!</button>
` + 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(`
<div>
<p><b>Report %v</b></p>
<p>By User: <a class="user" href="nostr:%v">%v</a></p>
<p>About User: <a class="user" href="nostr:%v">%v</a></p>`,
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(`
<p>
About Event: <ul>
<p>Kind: %v</p>
<p>Tags: %v</p>
<p>Content: %v</p>
</ul>
</p>`,
template.HTMLEscaper(aboutEvent.Kind),
template.HTMLEscaper(aboutEvent.Tags),
template.HTMLEscaper(aboutEvent.Content),
))
}
}
formattedReportsData += template.HTML(fmt.Sprintf(`
<p>Type: %v</p>`,
report.Type,
))
if report.Content != "" {
formattedReportsData += template.HTML(fmt.Sprintf(`
<p>Content: %v</p>
<div>
<button data-actionarg='[["e", "%v"],["p", "%v"]]' class="removefromrelay">Ban Reported User and Remove Report</button>
<button data-actionarg='[["e", "%v"]]' class="removefromrelay">Remove This Report</button>
<button data-actionarg='[["p", "%v"]]' class="removefromrelay">Ban User who wrote report</button>
</div>
</div>
<hr />`,
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(`
// <div>
// <p><b>Report %v</b></p>
// <p>By User: <a class="user" href="nostr:%v">%v</a></p>
// <p>About User: <a class="user" href="nostr:%v">%v</a></p>`,
// 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(`
// <p>
// About Event: <ul>
// <p>Kind: %v</p>
// <p>Tags: %v</p>
// <p>Content: %v</p>
// </ul>
// </p>`,
// template.HTMLEscaper(aboutEvent.Kind),
// template.HTMLEscaper(aboutEvent.Tags),
// template.HTMLEscaper(aboutEvent.Content),
// ))
// }
// }
// formattedReportsData += template.HTML(fmt.Sprintf(`
// <p>Type: %v</p>`,
// report.Type,
// ))
// if report.Content != "" {
// formattedReportsData += template.HTML(fmt.Sprintf(`
// <p>Content: %v</p>
// <div>
// <button data-actionarg='[["e", "%v"],["p", "%v"]]' class="removefromrelay">Ban Reported User and Remove Report</button>
// <button data-actionarg='[["e", "%v"]]' class="removefromrelay">Remove This Report</button>
// <button data-actionarg='[["p", "%v"]]' class="removefromrelay">Ban User who wrote report</button>
// </div>
// </div>
// <hr />`,
// 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(`
<div>Relay Name: %v</div>
<div>Relay Description: %v</div>
<div>Relay Owner: <a class="user" href="nostr:%v">%v</a></div>
<div>Relay Alternative Contact: %v</div>
<br />
<div><sub>This relay uses <a target="_blank" rel="noopener noreferrer" href="https://github.com/github-tijlxyz/khatru-invite">Khatru Invite</a>, which is build with <a target="_blank" rel="noopener noreferrer" href="https://github.com/fiatjaf/khatru">Khatru</a></sub></div>
`, 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) {

View File

@ -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

92
pages.go Normal file
View File

@ -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
}

View File

@ -1,32 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="{{.Relaydescription}}">
<script src="./src/main.ts" type="module"></script>
<title>{{.Relayname}} | {{.Pagetitle}}</title>
</head>
<body>
<div>
<h1>{{.Relayname}}</h1>
<p>{{.Relaydescription}}</p>
</div>
<hr />
<div>
<span>
<a href="/">information</a> -
<a href="/users">invite tree</a> -
<a href="/reports">admin reports viewer</a>
</span>
</div>
<hr />
<br />
<div>
{{.Pagecontent}}
</div>
</body>
</html>

View File

@ -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 := "<ul>"
for _, entry := range entries {
if entry.InvitedBy == invitedBy {
user := getUserInfo(context.TODO(), entry.PublicKey)
html += fmt.Sprintf(`
<li>
<a class="user" href="nostr:%s">%s</a>
<a data-actionarg='[["p", "%v"]]' class="rembtn removefromrelay">x</a>
%s
</li>`, template.HTMLEscapeString(user.Npub),
template.HTMLEscapeString(user.Name),
entry.PublicKey,
buildHTMLTree(entries, entry.PublicKey))
}
func isPublicKeyInWhitelist(pubkey string) bool {
if pubkey == s.RelayPubkey {
return true
}
html += "</ul>"
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
}
}

View File

@ -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})
}
}
}