use go templating instead of svelte client

This commit is contained in:
github-tijlxyz 2023-10-11 00:19:33 +02:00
parent 7018b5c3ec
commit 216b2aa70b
39 changed files with 923 additions and 3681 deletions

10
.env
View File

@ -1,8 +1,8 @@
# Nostr Standard Relay Info
RELAY_NAME=""
RELAY_DESCRIPTION=""
RELAY_PUBKEY=""
RELAY_CONTACT=""
RELAY_NAME="Name of the Relay"
RELAY_DESCRIPTION="Description of the relay"
RELAY_PUBKEY="07adfda9c5adc80881bb2a5220f6e3181e0c043b90fa115c4f183464022968e6"
RELAY_CONTACT="email@example.com"
# Custom invite-relay settings
INVITE_RELAY_MASTER="" # Master of the relay (pubkey hex)
INVITE_RELAY_MASTER="07adfda9c5adc80881bb2a5220f6e3181e0c043b90fa115c4f183464022968e6" # Master of the relay (pubkey hex)

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
whitelist.json
khatru-invite
.env
khatru-badgern-db
khatru-badgern-db

35
Dockerfile Normal file
View File

@ -0,0 +1,35 @@
FROM node:latest AS ui-builder
WORKDIR /app
COPY . .
WORKDIR /app/ui
RUN yarn install
RUN yarn build
FROM golang:1.20 AS go-builder
WORKDIR /app
COPY . .
COPY --from=ui-builder /app/ui/dist /app/ui/dist
RUN go build -o app
FROM golang:1.20
WORKDIR /app
COPY --from=go-builder /app/app /app/app
COPY --from=ui-builder /app/ui/dist /app/ui/dist
EXPOSE 3334
CMD ["./app"]

View File

@ -3,6 +3,5 @@
A relay based on [Khatru](https://github.com/fiatjaf/khatru) with a invite hierarchy feature.
some notes before running:
1. change `ws://localhost:3334` in `ui/src/lib/consts.ts` to your relay url endpoint and build the UI
2. configure the relay settings in `.env`
3. manually add someone to the `whitelist.json` file, like this: `[{"pk":"07adfda9c5adc80881bb2a5220f6e3181e0c043b90fa115c4f183464022968e6","invited_by":""}]`
1. configure the relay settings in `.env`
2. manually add someone to the `whitelist.json` file, like this: `[{"pk":"07adfda9c5adc80881bb2a5220f6e3181e0c043b90fa115c4f183464022968e6","invited_by":""}]`

15
docker-compose.yaml Normal file
View File

@ -0,0 +1,15 @@
version: '3'
services:
khatru-invite:
restart: unless-stopped
build:
context: .
dockerfile: Dockerfile
ports:
- 3335:3334
volumes:
- ./whitelist.json:/app/whitelist.json
- ./khatru-badgern-db:/app/khatru-badgern-db
env_file:
- .env

14
go.mod
View File

@ -1,17 +1,17 @@
module github.com/github-tijlxyz/khatru-invite
go 1.21.1
go 1.20
require (
github.com/fiatjaf/khatru v0.0.0-20230916185141-24d1e3aebd67
github.com/fiatjaf/khatru v0.0.0-20231003113207-bbe186494e68
github.com/joho/godotenv v1.5.1
github.com/nbd-wtf/go-nostr v0.20.0
)
require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/bmatsuo/lmdb-go v1.8.0 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
github.com/btcsuite/btcd/btcutil v1.1.3 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
@ -26,16 +26,15 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect
github.com/golang/protobuf v1.3.1 // indirect
github.com/golang/snappy v0.0.3 // indirect
github.com/golang/protobuf v1.4.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/flatbuffers v1.12.1 // indirect
github.com/jmoiron/sqlx v1.3.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.16.5 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-sqlite3 v1.14.6 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/puzpuzpuz/xsync v1.5.2 // indirect
github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect
github.com/rs/cors v1.7.0 // indirect
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
github.com/tidwall/gjson v1.14.4 // indirect
@ -47,4 +46,5 @@ require (
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.8.0 // indirect
google.golang.org/protobuf v1.23.0 // indirect
)

100
go.sum
View File

@ -1,24 +1,47 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/bmatsuo/lmdb-go v1.8.0 h1:ohf3Q4xjXZBKh4AayUY4bb2CXuhRAI8BYGlJq08EfNA=
github.com/bmatsuo/lmdb-go v1.8.0/go.mod h1:wWPZmKdOAZsl4qOqkowQ1aCrFie1HU8gWloHMCeAUdM=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U=
github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ=
github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
github.com/dgraph-io/badger/v4 v4.1.0 h1:E38jc0f+RATYrycSUf9LMv/t47XAy+3CApyYSq4APOQ=
github.com/dgraph-io/badger/v4 v4.1.0/go.mod h1:P50u28d39ibBRmIJuQC/NSdBOg46HnHw7al2SW5QRHg=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
@ -29,9 +52,10 @@ 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-20230916185141-24d1e3aebd67 h1:nsaUBS6rjAZCvPDgGUxG3tzfZnx6DX9iUBqPtDWDT/g=
github.com/fiatjaf/khatru v0.0.0-20230916185141-24d1e3aebd67/go.mod h1:XDX+UIURFlTrAAw/ya7M5YRC4ft2Qm2gAAvs3ELoVps=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
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=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
@ -46,44 +70,65 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE=
github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/nbd-wtf/go-nostr v0.20.0 h1:97SYhg68jWh5G1bW1g454hA0dTV7btwtPg836n4no0o=
github.com/nbd-wtf/go-nostr v0.20.0/go.mod h1:iFfiZr8YYSC1vmdUei0VfDB7GH/RjS3cbmiD1I5BKyo=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/puzpuzpuz/xsync v1.5.2 h1:yRAP4wqSOZG+/4pxJ08fPTwrfL0IzE/LKQ/cw509qGY=
github.com/puzpuzpuz/xsync v1.5.2/go.mod h1:K98BYhX3k1dQ2M63t1YNVDanbwUPmBCAhNmVrrxfiGg=
github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU=
github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
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/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
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=
@ -98,6 +143,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -109,13 +155,17 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
@ -127,15 +177,23 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -154,8 +212,20 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -1,24 +1,199 @@
package main
import (
"context"
"embed"
"encoding/json"
"fmt"
"html/template"
"net/http"
"strings"
"github.com/nbd-wtf/go-nostr"
)
// embed ui files
//go:embed ui/dist/*
var uiContent embed.FS
var dist embed.FS
func embeddedUIHandler(w http.ResponseWriter, r *http.Request) {
path := "ui/dist" + r.URL.Path
func inviteTreeHandler(w http.ResponseWriter, r *http.Request) {
formattedInviteData := buildHTMLTree(whitelist, "")
if r.URL.Path == "/" {
path = "ui/dist/index.html"
data := map[string]interface{}{
"Relayname": relayName,
"Relaydescription": relayDescription,
"Pagetitle": "Invite Hierarchy",
"Pagecontent": `
<input type="text" id="inviteuser-input" placeholder="npub1..." /><button class="inviteuser">Invite!</button>
` + formattedInviteData,
}
data, err := uiContent.ReadFile(path)
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 reportsViewerHandler(w http.ResponseWriter, r *http.Request) {
var formattedReportsData template.HTML = ""
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 {
var 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": relayName,
"Relaydescription": 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
}
}
func homePageHandler(w http.ResponseWriter, r *http.Request) {
relayOwnerInfo := getUserInfo(context.Background(), relayPubkey)
data := map[string]interface{}{
"Relayname": relayName,
"Relaydescription": 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>
`, relayName, relayDescription, relayOwnerInfo.Npub, relayOwnerInfo.Name, 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)
}
}
func staticHandler(prefix string, w http.ResponseWriter, r *http.Request) {
path := prefix + r.URL.Path
data, err := dist.ReadFile(path)
if err != nil {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
@ -38,31 +213,3 @@ func embeddedUIHandler(w http.ResponseWriter, r *http.Request) {
return
}
}
func inviteDataApiHandler(w http.ResponseWriter, re *http.Request) {
jsonBytes, err := json.Marshal(whitelist)
if err != nil {
http.Error(w, "internal server error 00", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.WriteHeader(http.StatusOK)
if _, err := w.Write(jsonBytes); err != nil {
http.Error(w, "internal server error 01", http.StatusInternalServerError)
}
}
func relayMasterApiHandler(w http.ResponseWriter, re *http.Request) {
jsonBytes, err := json.Marshal(relayMaster)
if err != nil {
http.Error(w, "internal server error 10", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.WriteHeader(http.StatusOK)
if _, err := w.Write(jsonBytes); err != nil {
http.Error(w, "internal server error 11", http.StatusInternalServerError)
}
}

42
main.go
View File

@ -14,8 +14,14 @@ import (
"github.com/joho/godotenv"
)
var relayMaster string
var db badgern.BadgerBackend
var (
relayMaster string
db badgern.BadgerBackend
relayName string = ""
relayPubkey string = ""
relayDescription string = "none"
relayContact string = "none"
)
func main() {
// save whitelist on shutdown
@ -41,11 +47,17 @@ func main() {
relay := khatru.NewRelay()
relayMaster = os.Getenv("INVITE_RELAY_MASTER")
// add information here!
relay.Name = "a invite relay"
relay.PubKey = ""
relay.Contact = ""
relayName = os.Getenv("RELAY_NAME")
relayPubkey = os.Getenv("RELAY_PUBKEY")
relayDescription = os.Getenv("RELAY_DESCRIPTION")
relayContact = os.Getenv("RELAY_CONTACT")
relay.Name = relayName
relay.PubKey = relayPubkey
relay.Description = relayDescription
relay.Contact = relayContact
// load whitelist storage
if err := loadWhitelist(); err != nil {
@ -65,12 +77,10 @@ func main() {
relay.RejectEvent = append(relay.RejectEvent, whitelistRejecter)
// invitedata api
relay.Router().HandleFunc("/invitedata", inviteDataApiHandler)
relay.Router().HandleFunc("/relaymaster", relayMasterApiHandler)
// ui
relay.Router().HandleFunc("/", embeddedUIHandler)
relay.Router().HandleFunc("/reports", reportsViewerHandler)
relay.Router().HandleFunc("/users", inviteTreeHandler)
relay.Router().HandleFunc("/", redirectHandler)
fmt.Println("running on :3334")
http.ListenAndServe(":3334", relay)
@ -78,9 +88,9 @@ func main() {
// save whitelist on shutdown
func handleSignals() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
<-sigCh
saveWhitelist()
os.Exit(0)
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
<-sigCh
saveWhitelist()
os.Exit(0)
}

49
nostr.go Normal file
View File

@ -0,0 +1,49 @@
package main
import (
"context"
"time"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
)
type SimpleUserInfo struct {
Npub string
Name string
Picture string
Time time.Time
}
var (
userInfoCache = make(map[string]SimpleUserInfo)
)
func getUserInfo(ctx context.Context, hexpubkey string) SimpleUserInfo {
// check if in cache
v, o := userInfoCache[hexpubkey]
if o {
if !(time.Since(v.Time) > 2*time.Hour) { // use cache for 2 hours
return v
}
}
npub, _ := nip19.EncodePublicKey(hexpubkey)
var name string = string(npub)
var picture string = ""
evts, err := db.QueryEvents(ctx, nostr.Filter{
Authors: []string{hexpubkey},
Kinds: []int{0},
Limit: 1,
})
if err != nil {
return SimpleUserInfo{npub, name, picture, time.Now()}
}
for ev := range evts {
name, picture = getProfileInfoFromJson(ev.Content)
}
userInfoCache[hexpubkey] = SimpleUserInfo{npub, name, picture, time.Now()}
return SimpleUserInfo{npub, name, picture, time.Now()}
}

View File

@ -1,13 +0,0 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View File

@ -1,30 +0,0 @@
module.exports = {
root: true,
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:svelte/recommended",
"prettier",
],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
parserOptions: {
sourceType: "module",
ecmaVersion: 2020,
extraFileExtensions: [".svelte"],
},
env: {
browser: true,
es2017: true,
node: true,
},
overrides: [
{
files: ["*.svelte"],
parser: "svelte-eslint-parser",
parserOptions: {
parser: "@typescript-eslint/parser",
},
},
],
};

24
ui/.gitignore vendored
View File

@ -1,2 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -1,13 +0,0 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View File

@ -1,16 +1,32 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Invite Relay</title>
<link rel="icon" href="data:," />
<meta name="description" content="Invite Relay UI" />
</head>
<!DOCTYPE html>
<html>
<body>
<noscript>Please enable JavaScript to run this app.</noscript>
<div id="app"></div>
<script type="module" src="./src/main.js"></script>
</body>
</html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="{{.Relaydescription}}">
<script src="./src/lib/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

@ -1,31 +1,20 @@
{
"name": "inviterelay-ui",
"name": "ui",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
"build": "tsc && vite build",
"preview": "vite preview"
},
"type": "module",
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.0.2",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.24",
"sass": "^1.45.0",
"svelte": "^3.44.0",
"svelte-flatpickr": "^3.2.6",
"svelte-spa-router": "^3.2.0",
"tailwindcss": "^3.3.2",
"vite": "^4.0.4",
"@nostr-dev-kit/ndk": "^0.8.23",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.9",
"eslint-plugin-svelte": "^2.33.0",
"nostr-tools": "^1.14.2",
"prettier": "^3.0.3",
"prettier-plugin-svelte": "^3.0.3"
"@nostr-dev-kit/ndk": "^1.3.1",
"nostr-tools": "^1.16.0",
"typescript": "^5.0.2",
"vite": "^4.4.5"
},
"dependencies": {}
"dependencies": {
"@nostr-dev-kit/ndk-cache-dexie": "^1.3.2"
}
}

View File

@ -1,6 +0,0 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View File

@ -1,3 +0,0 @@
@tailwind base;
@tailwind utilities;
@tailwind components;

View File

@ -1,97 +0,0 @@
<script lang="ts">
import "./App.css";
import Hierarchy from "./components/Hierarchy.svelte";
import Invite from "./components/Invite.svelte";
import { onMount } from "svelte";
import { ndk, relayMaster, userPublickey } from "./lib/nostr";
import { NDKNip07Signer } from "@nostr-dev-kit/ndk";
import { buildHierarchy } from "./lib/utils";
import { nip19 } from "nostr-tools";
import AdminView from "./components/AdminView.svelte";
let adminView = false;
async function login() {
const signer = new NDKNip07Signer();
$ndk.signer = signer;
ndk.set($ndk);
$userPublickey = (await $ndk.signer.user()).npub;
userPublickey.set($userPublickey);
}
async function fetchData() {
// Fetch Invite Data
const response = await fetch("/invitedata");
invitedata = Object.values(await response.json());
hierarchy = buildHierarchy(invitedata, { pk: "", invited_by: "" });
// Fetch Relay Master Pubkey
const response0 = await fetch("/relaymaster");
$relayMaster = await response0.json();
relayMaster.set($relayMaster);
}
let invitedata = [];
let hierarchy = [];
onMount(() => {
addEventListener("load", (e) => {
setTimeout(() => {
login();
}, 1);
});
fetchData();
});
</script>
<article class="font-sans px-4 py-6 lg:max-w-7xl lg:pt-6 lg:pb-28">
<h1 class="text-xl">Invite Relay</h1>
{#if adminView == true}
<button
on:click={() => (adminView = false)}
type="button"
class="inline-flex mr-2 items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
>Leave Reports Viewer</button
>
<p>
You are{$userPublickey == nip19.npubEncode($relayMaster) ? "" : " not"} logged
in as a relay master
</p>
<AdminView />
{:else if adminView == false}
<div>
{#if $userPublickey === undefined}
<button
on:click={login}
type="button"
class="inline-flex mr-2 items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
>Login with NIP07</button
>
{/if}
</div>
{#if $userPublickey !== undefined}
<div>
<button
on:click={() => (adminView = true)}
type="button"
class="inline-flex mr-2 items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
>Open Reports Viewer</button
>
</div>
{/if}
{#if invitedata.find((p) => $userPublickey == nip19.npubEncode(p.pk))}
<div>
<h3>Invite Someone</h3>
<div>
<Invite reload={fetchData} />
</div>
</div>
{/if}
<div>
<h3>Current Hierarchy</h3>
<div>
<Hierarchy {hierarchy} reload={fetchData} />
</div>
</div>
{/if}
</article>

View File

@ -1,41 +0,0 @@
<script>
import { NDKRelay, NDKRelaySet } from "@nostr-dev-kit/ndk";
import { ndk } from "../lib/nostr";
import { onMount } from "svelte";
import ReportEvent from "./ReportEvent.svelte";
import { relayUrl } from "../lib/consts";
let events = [];
onMount(async () => {
try {
// dont know why this needs so much code
let specificRelay = [new NDKRelay(relayUrl)];
const relaySet = new NDKRelaySet(specificRelay, $ndk);
relaySet.relays.forEach(async (relay) => {
await relay.connect().catch((err) => {
console.log("error while connecting to relay", err);
});
relay.on("connect", () => {
console.log("connected");
});
});
let filter = { kinds: [1984], limit: 250 };
let options = { closeOnEose: true };
let es = await $ndk.fetchEvents(filter, options, relaySet);
events = Array.from(es);
} catch (error) {
console.log("error while getting feed", error);
}
});
</script>
<div>
{#each events as event}
<ReportEvent {event} />
{/each}
{#if events.length == 0}
<span>Didn't found any events</span>
{/if}
</div>

View File

@ -1,14 +0,0 @@
<script lang="js">
import TreeNode from "./TreeNode.svelte";
export let reload;
export let hierarchy;
</script>
<div class="px-4">
<ul class="list-disc ml-4">
{#each hierarchy as person (person.pk)}
<TreeNode {reload} {person} />
{/each}
</ul>
</div>

View File

@ -1,52 +0,0 @@
<script>
import { NDKEvent, NDKRelay, NDKRelaySet } from "@nostr-dev-kit/ndk";
import { ndk } from "../lib/nostr";
import { nip19 } from "nostr-tools";
import { relayUrl } from "../lib/consts";
let pubKeyToInvite = "";
export let reload;
async function invite() {
if (!pubKeyToInvite) return;
try {
// only publish to the relay in question, dont know why this needs so much code
let specificRelay = [new NDKRelay(relayUrl)];
const relaySet = new NDKRelaySet(specificRelay, $ndk);
relaySet.relays.forEach(async (relay) => {
await relay.connect().catch((err) => {
console.log("RELAY CONNECT ERROR");
console.error(err);
});
relay.on("connect", () => {
console.log(relay.url, "connected");
});
});
const pk = pubKeyToInvite.startsWith("npub")
? nip19.decode(pubKeyToInvite).data
: pubKeyToInvite;
const event = new NDKEvent($ndk);
event.kind = 20201;
event.tags.push(["p", pk.toString()]);
await event.publish(relaySet).then(() => reload());
pubKeyToInvite = "";
} catch (error) {
console.log("error while publishing", error);
}
}
</script>
<div class="relative flex items-stretch flex-grow focus-within:z-10">
<input
bind:value={pubKeyToInvite}
class="focus:ring-indigo-500 focus:border-indigo-500 block w-full rounded-none rounded-l-md sm:text-sm border-gray-300"
placeholder="hex or npub"
/>
<button
on:click={invite}
type="submit"
class="-ml-px relative inline-flex items-center space-x-2 px-3 py-2 border border-gray-300 text-sm font-medium rounded-r-md text-gray-700 bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-white"
>Go</button
>
</div>

View File

@ -1,181 +0,0 @@
<script>
import { NDKEvent, NDKRelay, NDKRelaySet } from "@nostr-dev-kit/ndk";
import { relayUrl } from "../lib/consts";
import { ndk } from "../lib/nostr";
export let event;
let show = true;
async function dismissReportEvent() {
let confirmation = confirm(
"Are you sure you want to delete this event? (You can only do this if you are a relay master)",
);
if (confirmation) {
try {
// remove report event
// only publish to the relay in question, dont know why this needs so much code
let specificRelay = [new NDKRelay(relayUrl)];
const relaySet = new NDKRelaySet(specificRelay, $ndk);
relaySet.relays.forEach(async (relay) => {
await relay.connect().catch((err) => {
console.log("RELAY CONNECT ERROR");
console.error(err);
});
relay.on("connect", () => {
console.log(relay.url, "connected");
});
});
const newevent = new NDKEvent($ndk);
newevent.kind = 20203;
newevent.tags.push(["e", event.id]);
await newevent.publish(relaySet);
show = false;
} catch (error) {
console.log("error publishing event", error);
}
}
}
async function removeUser(username, pk) {
let confirmation = confirm(
`Are you sure you want to remove ${
username ? username : pk
}? All people they invited will also be removed. (you can only do this if you invited this user or are the relay admin)`,
);
if (confirmation) {
try {
// only publish to the relay in question, dont know why this needs so much code
let specificRelay = [new NDKRelay(relayUrl)];
const relaySet = new NDKRelaySet(specificRelay, $ndk);
relaySet.relays.forEach(async (relay) => {
await relay.connect().catch((err) => {
console.log("error while connecting to relay", err);
});
relay.on("connect", () => {
console.log("connected");
});
});
const newevent = new NDKEvent($ndk);
newevent.kind = 20202;
newevent.tags.push(["p", pk]);
await newevent.publish(relaySet);
} catch (error) {
console.log("error while publishing", error);
}
}
}
</script>
{#if show}
<div class="rounded-lg border border-slate-500 bg-slate-50 w-full p-4 mt-8">
<div class="columns-2 p-0 m-0">
<div class="bg-white rounded-lg px-4 py-2">
from
{#await event.author?.fetchProfile()}
<a href={`nostr:${event.author.npub}`}>...</a>
{:then profile}
<img
class="h-7 w-7 m-0 p-0 rounded-full inline"
src={profile &&
JSON.parse(Array.from(profile)[0]?.content)?.picture}
alt=""
/>
<a class="hover:underline" href={`nostr:${event.author.npub}`}
>{profile && JSON.parse(Array.from(profile)[0]?.content)?.name}</a
>
{/await}
</div>
<div class="bg-white rounded-lg px-4 py-2">
{#if event?.tags.find((e) => e[0] == "p")?.[0] && event?.tags.find((e) => e[0] == "p")?.[1]}
to
{#await $ndk
.getUser({ hexpubkey: event.tags.find((e) => e[0] == "p")?.[1] })
.fetchProfile()}
<img class="h-7 w-7 m-0 p-0 rounded-full inline" src="" alt="" />
<a href={`nostr:${event.tags.find((e) => e[0] == "p")?.[1]}`}>...</a
>
{:then profile}
<img
class="h-7 w-7 m-0 p-0 rounded-full inline"
src={profile &&
JSON.parse(Array.from(profile)[0]?.content)?.picture}
alt=""
/>
<a
class="hover:underline"
href={`nostr:${event.tags.find((e) => e[0] == "p")?.[1]}`}
>{profile &&
JSON.parse(Array.from(profile)[0]?.content)?.name.length <= 16
? JSON.parse(Array.from(profile)[0]?.content)?.name
: JSON.parse(Array.from(profile)[0]?.content)?.name.substring(
0,
13,
) + "..."}</a
>
{/await}
{:else}
huh, nothing here
{/if}
</div>
</div>
<div class="bg-white max-h-64 scroll-auto rounded-lg m-0 p-4 mt-2">
{#if event?.tags.find((e) => e[0] == "e")?.[0] && event?.tags.find((e) => e[0] == "e")?.[1] && event?.tags.find((e) => e[0] == "e")?.[2]}
{event?.tags.find((e) => e[0] == "e")?.[2]}
{:else if event?.tags.find((e) => e[0] == "p")?.[0] && event?.tags.find((e) => e[0] == "p")?.[1] && event?.tags.find((e) => e[0] == "p")?.[2]}
{event?.tags.find((e) => e[0] == "p")?.[2]}
{/if}
{#if event?.content}
<p class="text-gray-500 m-0 p-0">{event.content}</p>
{/if}
</div>
{#if event?.tags.find((e) => e[0] == "e")?.[0] && event?.tags.find((e) => e[0] == "e")?.[1]}
<div
class="bg-white max-h-64 max-w-full overflow-y-scroll rounded-lg m-0 p-4 mt-2"
>
{#await $ndk.fetchEvent( { ids: [event?.tags.find((e) => e[0] == "e")?.[1]] }, )}
<p class="text-gray-500 p-0 m-0">... (can we find this event?)</p>
{:then theevent}
<p class="m-0 p-0">{theevent.content}</p>
<p class="text-gray-500 m-0 p-0">
kind: <span class="text-black inline">{theevent.kind}</span>
</p>
<p class="text-gray-500 m-0 p-0">
tags: {#each theevent.tags as tag}<p>
{tag[0]}: <span class="text-black">{tag[1]}</span>
</p>{/each}
</p>
{/await}
</div>
{/if}
<div class="p-0 mb-0 mx-0 mt-2">
<div class="columns-4 inline">
<a
href={`nostr:${
event?.tags.find((e) => e[0] == "e")?.[0] &&
event?.tags.find((e) => e[0] == "e")?.[1]
? event?.tags.find((e) => e[0] == "e")?.[1]
: event?.tags.find((e) => e[0] == "p")?.[1]
}`}
class="rounded-lg inline bg-slate-100 p-2 cursor-pointer hover:bg-white"
>
Open in client
</a>
<button
on:click={dismissReportEvent}
class="rounded-lg inline bg-green-500 p-2 cursor-pointer hover:bg-green-400"
>
Remove This Report Event
</button>
<button
on:click={() =>
removeUser("user", event.tags.find((e) => e[0] == "p")?.[1])}
class="rounded-lg inline bg-red-500 p-2 cursor-pointer hover:bg-red-400"
>
Exlude reported user
</button>
</div>
</div>
</div>
{/if}

View File

@ -1,76 +0,0 @@
<script lang="js">
import { onMount } from "svelte";
import { ndk, relayMaster, userPublickey } from "../lib/nostr";
import TreeNode from "./TreeNode.svelte";
import { nip19 } from "nostr-tools";
import { NDKEvent, NDKRelay, NDKRelaySet, NDKUser } from "@nostr-dev-kit/ndk";
import { relayUrl } from "../lib/consts";
export let person;
export let reload;
let username = "...";
onMount(async () => {
try {
let user = $ndk.getUser({
hexpubkey: person.pk,
});
await user.fetchProfile();
username = user.profile.name;
} catch (error) {
console.log(error);
}
});
async function removeThisUser() {
let confirmation = confirm(
`Are you sure you want to remove ${
username ? username : person.pk
}? All people they invited will also be removed. (you can only do this if you invited this user or are the relay admin)`,
);
if (confirmation) {
try {
// only publish to the relay in question, dont know why this needs so much code
let specificRelay = [new NDKRelay(relayUrl)];
const relaySet = new NDKRelaySet(specificRelay, $ndk);
relaySet.relays.forEach(async (relay) => {
await relay.connect().catch((err) => {
console.log("error while connecting to relay", err);
});
relay.on("connect", () => {
console.log("connected");
});
});
const event = new NDKEvent($ndk);
event.kind = 20202;
event.tags.push(["p", person.pk]);
await event.publish(relaySet).then(() => reload());
} catch (error) {
console.log("error while publishing", error);
}
}
}
</script>
<li>
<span>
<a
class="inline hover:underline"
href={`nostr:${nip19.npubEncode(person.pk)}`}
>{#if username}{username}{:else}{nip19.npubEncode(person.pk)}{/if}</a
>
{#if $userPublickey == nip19.npubEncode(person.invited_by) || $userPublickey == $relayMaster}<button
on:click={removeThisUser}
class="inline cusor-pointer font-semibold text-red-500">[-]</button
>{/if}{#if $userPublickey == nip19.npubEncode(person.pk)}
<span> (you)</span>
{/if}
</span>
{#if person.children && person.children.length > 0}
<ul class="list-disc ml-2">
{#each person.children as child (child.pk)}
<TreeNode {reload} person={child} />
{/each}
</ul>
{/if}
</li>

85
ui/src/lib/actions.ts Normal file
View File

@ -0,0 +1,85 @@
import { NDKEvent, NDKRelay, NDKRelaySet } from '@nostr-dev-kit/ndk';
import { nip19 } from 'nostr-tools';
import { ndk, relayUrl } from './main';
// Handle Buttons
export function loadActions() {
document.querySelectorAll(".removefromrelay").forEach((btn) => {
btn.addEventListener('click', () => {
const arg = btn.getAttribute('data-actionarg');
if (arg) {
const jsonArg = JSON.parse(arg);
if (jsonArg) {
removeFromRelay(jsonArg)
}
};
})
})
document.querySelectorAll(".inviteuser").forEach((btn) => {
btn.addEventListener('click', () => {
inviteUser();
})
})
async function inviteUser() {
// only publish to the relay in question, dont know why this needs so much code
let specificRelay = new Set<NDKRelay>
specificRelay.add(new NDKRelay(relayUrl))
const relaySet = new NDKRelaySet(specificRelay, ndk);
relaySet.relays.forEach(async (relay) => {
await relay.connect().catch((err) => {
console.log("RELAY CONNECT ERROR");
console.error(err);
});
relay.on("connect", () => {
console.log(relay.url, "connected");
});
});
var input = (<HTMLInputElement>document.getElementById('inviteuser-input')).value;
let p = input;
if (input.startsWith("npub")) {
let h = nip19.decode(input).data;
if (typeof h == 'string') {
p = h;
}
}
const event = new NDKEvent(ndk)
event.kind = 20201;
event.tags = [['p', p]];
event.content = "";
const relays = await event.publish(relaySet);
relays.forEach(() => {
setTimeout(() => {
window.location.href = ""
}, 128)
});
}
async function removeFromRelay(tags: string[][]) {
// only publish to the relay in question, dont know why this needs so much code
let specificRelay = new Set<NDKRelay>
specificRelay.add(new NDKRelay(relayUrl))
const relaySet = new NDKRelaySet(specificRelay, ndk);
relaySet.relays.forEach(async (relay) => {
await relay.connect().catch((err) => {
console.log("RELAY CONNECT ERROR");
console.error(err);
});
relay.on("connect", () => {
console.log(relay.url, "connected");
});
});
const event = new NDKEvent(ndk)
event.kind = 20202;
event.tags = tags;
event.content = "";
const relays = await event.publish();
relays.forEach(() => {
setTimeout(() => {
window.location.href = ""
}, 128)
});
}
}

View File

@ -1,2 +0,0 @@
export const relayUrl = "ws://localhost:3334"; // Important: Change this and rebuild ui if needed
export const someRelays = ["wss://nos.lol", "wss://relay.snort.social"];

41
ui/src/lib/main.ts Normal file
View File

@ -0,0 +1,41 @@
import NDK, { NDKNip07Signer } from '@nostr-dev-kit/ndk';
import '../style.css';
import { loadUsernames } from './user.js';
import { loadActions } from './actions.js';
// import NDKCacheAdapterDexie from "@nostr-dev-kit/ndk-cache-dexie";
export let ndk: NDK;
export let relayUrl: string;
async function load() {
// get relay websocket url
var loc = window.location;
if (loc.protocol === "https:") {
relayUrl = "wss:";
} else {
relayUrl = "ws:";
}
relayUrl += "//" + loc.host;
// init NDK
// const dexieAdapter = new NDKCacheAdapterDexie({ dbName: 'khatru-invite-ndk-cache' })
const nip07signer = new NDKNip07Signer()
ndk = new NDK({
explicitRelayUrls: [
relayUrl,
"wss://nos.lol",
"wss://nostr-pub.wellorder.net",
], signer: nip07signer
});
await ndk.connect();
loadActions();
loadUsernames();
}
// load
load();

View File

@ -1,13 +0,0 @@
import NDK from "@nostr-dev-kit/ndk";
import { writable, type Writable } from "svelte/store";
import { relayUrl, someRelays } from "./consts";
const relays = [relayUrl, ...someRelays];
const Ndk: NDK = new NDK({ explicitRelayUrls: relays });
Ndk.connect().then(() => console.log("ndk connected"));
export const ndk: Writable<NDK> = writable(Ndk);
export const userPublickey: Writable<string | undefined> = writable(undefined);
export const relayMaster: Writable<string> = writable("");

19
ui/src/lib/user.ts Normal file
View File

@ -0,0 +1,19 @@
// import { ndk } from "./main.js";
export async function loadUsernames() {
/*
try to get username from other realy for each user
that isn't loaded by the server
*/
// let b = document.querySelectorAll(".user");
// b.forEach(async (e) => {
// if (e.innerHTML.startsWith("npub1")) {
// let npub1 = e.innerHTML;
// let u = ndk.getUser({ npub: npub1 });
// let p = await u.fetchProfile();
// if (p?.name) e.innerHTML = p.name;
// if (p?.displayName) e.innerHTML = p.displayName;
// }
// });
}

View File

@ -1,18 +0,0 @@
interface TreeNodeType {
pk: string;
invited_by: string;
children?: TreeNodeType[];
}
export function buildHierarchy(
data: TreeNodeType[],
parent: TreeNodeType | null = null,
): TreeNodeType[] {
const children = data.filter(
(item) => item.invited_by === (parent ? parent.pk : null),
);
return children.map((child) => ({
...child,
children: buildHierarchy(data, child),
}));
}

View File

@ -1,7 +0,0 @@
import App from "./App.svelte";
const app = new App({
target: document.getElementById("app"),
});
export default app;

9
ui/src/style.css Normal file
View File

@ -0,0 +1,9 @@
.inline {
display: inline;
}
.rembtn {
margin-left: 1px;
color: red;
cursor: pointer;
}

1
ui/src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -1,8 +0,0 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{html,js,svelte}"],
theme: {
extend: {},
},
plugins: [require("@tailwindcss/forms"), require("@tailwindcss/typography")],
};

34
ui/tsconfig.json Normal file
View File

@ -0,0 +1,34 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
// added
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
/* Bundler mode */
// "moduleResolution": "Bundler",
// "allowImportingTsExtensions": true,
"noEmit": true,
"allowJs": true,
"paths": {
"lib/*": ["./src/lib/*"],
},
/* Linting */
"strict": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}

View File

@ -1,26 +0,0 @@
import { defineConfig } from "vite";
import { svelte, vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import postcss from "postcss";
// see https://vitejs.dev/config
export default defineConfig({
envPrefix: "UI",
base: "./",
build: {
chunkSizeWarningLimit: 1000,
reportCompressedSize: false,
},
plugins: [
svelte({
preprocess: [vitePreprocess()],
}),
],
css: {
postcss,
},
resolve: {
alias: {
"@": __dirname + "/src",
},
},
});

File diff suppressed because it is too large Load Diff

View File

@ -2,22 +2,47 @@ package main
import (
"context"
"encoding/json"
"fmt"
"html/template"
"log"
"github.com/nbd-wtf/go-nostr"
)
func isPkInWhitelist(targetPk string) bool {
for i := 0; i < len(whitelist); i++ {
if whitelist[i].Pk == targetPk {
return true
}
}
return false
func buildHTMLTree(entries []WhitelistEntry, invitedBy string) template.HTML {
html := "<ul>"
for _, entry := range entries {
if entry.InvitedBy == invitedBy {
user := getUserInfo(context.TODO(), entry.Pk)
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.Pk,
buildHTMLTree(entries, entry.Pk))
}
}
html += "</ul>"
return template.HTML(html)
}
func deleteFromWhitelistRecursively (ctx context.Context, target string) {
var updatedWhitelist []User
func isPkInWhitelist(targetPk string) bool {
for i := 0; i < len(whitelist); i++ {
if whitelist[i].Pk == targetPk {
return true
}
}
return false
}
func deleteFromWhitelistRecursively(ctx context.Context, target string) {
var updatedWhitelist []WhitelistEntry
var queue []string
// Remove from whitelist
@ -26,7 +51,7 @@ func deleteFromWhitelistRecursively (ctx context.Context, target string) {
updatedWhitelist = append(updatedWhitelist, user)
}
if user.InvitedBy == target {
queue = append(queue, user.Pk);
queue = append(queue, user.Pk)
}
}
whitelist = updatedWhitelist
@ -48,3 +73,34 @@ func deleteFromWhitelistRecursively (ctx context.Context, target string) {
deleteFromWhitelistRecursively(ctx, pk)
}
}
func getProfileInfoFromJson(jsonStr string) (string, string) {
fieldOrder := []string{"displayName", "display_name", "username", "name"}
var data map[string]interface{}
err := json.Unmarshal([]byte(jsonStr), &data)
if err != nil {
fmt.Println("Error parsing JSON:", err)
return "", ""
}
var displayname string = "..."
var picture string = ""
for _, fieldName := range fieldOrder {
if val, ok := data[fieldName]; ok {
if strVal, ok := val.(string); ok && strVal != "" {
if fieldName == "picture" {
picture = strVal
}
if fieldName == "name" {
displayname = strVal
} else if displayname == "" {
displayname = strVal
}
}
}
}
return displayname, picture
}

View File

@ -9,12 +9,12 @@ import (
"github.com/nbd-wtf/go-nostr"
)
type User struct {
Pk string `json:"pk"`
type WhitelistEntry struct {
Pk string `json:"pk"`
InvitedBy string `json:"invited_by"`
}
var whitelist []User
var whitelist []WhitelistEntry
func whitelistRejecter(ctx context.Context, evt *nostr.Event) (reject bool, msg string) {
@ -22,44 +22,51 @@ func whitelistRejecter(ctx context.Context, evt *nostr.Event) (reject bool, msg
if !isPkInWhitelist(evt.PubKey) {
return true, "You are not invited to this relay"
}
// 20201 = user invites new user
if (evt.Kind == 20201) {
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, User{Pk: tag.Value(), InvitedBy: evt.PubKey})
whitelist = append(whitelist, WhitelistEntry{Pk: tag.Value(), InvitedBy: evt.PubKey})
}
}
}
}
// 20202 = user removes user they invited or admin removes invite
if (evt.Kind == 20202) {
// 20202
// p tag = user removes user they invited or admin removes user
// e tag = admin removes event
if evt.Kind == 20202 {
pTags := evt.Tags.GetAll([]string{"p"})
for _, tag := range pTags {
for _, user := range whitelist {
if user.Pk == tag.Value() && (user.InvitedBy == evt.PubKey || evt.PubKey == relayMaster) {
/*
1: User in whitelist
2: Cant remove self
3: User should have invited user OR be relay admin
*/
if user.Pk == tag.Value() && evt.PubKey != tag.Value() && (user.InvitedBy == evt.PubKey || evt.PubKey == relayMaster) {
log.Println("deleting user", tag.Value())
deleteFromWhitelistRecursively(ctx, tag.Value())
}
}
}
}
if evt.PubKey == relayMaster {
eTags := evt.Tags.GetAll([]string{"e"})
for _, tag := range eTags {
filter := nostr.Filter{
IDs: []string{tag.Value()},
}
events, _ := db.QueryEvents(ctx, filter)
// 20203 = admin deletes event
if (evt.Kind == 20203 && evt.PubKey == relayMaster) {
eTags := evt.Tags.GetAll([]string{"e"})
for _, tag := range eTags {
filter := nostr.Filter{
IDs: []string{tag.Value()},
}
events, _ := db.QueryEvents(ctx, filter);
for ev := range events {
err := db.DeleteEvent(ctx, ev)
if err != nil {
log.Println("error while deleting event", err)
for ev := range events {
log.Println("deleting evemt", ev.ID)
err := db.DeleteEvent(ctx, ev)
if err != nil {
log.Println("error while deleting event", err)
}
}
}
}
@ -69,35 +76,35 @@ func whitelistRejecter(ctx context.Context, evt *nostr.Event) (reject bool, msg
}
func loadWhitelist () error {
func loadWhitelist() error {
if _, err := os.Stat("whitelist.json"); os.IsNotExist(err) {
whitelist = []User{}
whitelist = []WhitelistEntry{}
return nil
} else if err != nil {
return err
}
fileContent, err := os.ReadFile("whitelist.json")
if err != nil {
return err
}
if err := json.Unmarshal(fileContent, &whitelist); err != nil {
return err
}
return nil
}
func saveWhitelist () error {
func saveWhitelist() error {
jsonBytes, err := json.Marshal(whitelist)
if err != nil {
return err
}
if err := os.WriteFile("whitelist.json", jsonBytes, 0644); err != nil {
return err
}
return nil
}