mirror of
https://github.com/github-tijlxyz/khatru-pyramid.git
synced 2025-04-19 18:31:18 +00:00
add relay admin and report viewer
This commit is contained in:
parent
feee34b683
commit
ef03fff5a6
8
.env
Normal file
8
.env
Normal file
@ -0,0 +1,8 @@
|
||||
# Nostr Standard Relay Info
|
||||
RELAY_NAME=""
|
||||
RELAY_DESCRIPTION=""
|
||||
RELAY_PUBKEY=""
|
||||
RELAY_CONTACT=""
|
||||
|
||||
# Custom invite-relay settings
|
||||
INVITE_MASTER="" # Master of the relay
|
1
go.mod
1
go.mod
@ -4,6 +4,7 @@ go 1.20
|
||||
|
||||
require (
|
||||
github.com/fiatjaf/khatru v0.0.2
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/nbd-wtf/go-nostr v0.20.0
|
||||
)
|
||||
|
||||
|
2
go.sum
2
go.sum
@ -50,6 +50,8 @@ github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
|
||||
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/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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
|
15
main.go
15
main.go
@ -11,8 +11,12 @@ import (
|
||||
|
||||
"github.com/fiatjaf/khatru"
|
||||
"github.com/fiatjaf/khatru/plugins/storage/badgern"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
var relayMaster string
|
||||
var db badgern.BadgerBackend
|
||||
|
||||
func main() {
|
||||
// save whitelist on shutdown
|
||||
var wg sync.WaitGroup
|
||||
@ -30,16 +34,25 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
// init env config
|
||||
godotenv.Load(".env")
|
||||
|
||||
// init relay
|
||||
relay := khatru.NewRelay()
|
||||
|
||||
relayMaster = os.Getenv("INVITE_MASTER")
|
||||
relay.Name = os.Getenv("RELAY_NAME")
|
||||
relay.Description = os.Getenv("RELAY_DESCRIPTION")
|
||||
relay.PubKey = os.Getenv("RELAY_PUBKEY")
|
||||
relay.Contact = os.Getenv("RELAY_CONTACT")
|
||||
|
||||
// load whitelist storage
|
||||
if err := loadWhitelist(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// load db
|
||||
db := badgern.BadgerBackend{Path: "/tmp/khatru-badgern-tmp"}
|
||||
db = badgern.BadgerBackend{Path: "/tmp/khatru-badgern-tmp"}
|
||||
if err := db.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
10
node_modules/.yarn-integrity
generated
vendored
Normal file
10
node_modules/.yarn-integrity
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"systemParams": "linux-x64-108",
|
||||
"modulesFolders": [],
|
||||
"flags": [],
|
||||
"linkedModules": [],
|
||||
"topLevelPatterns": [],
|
||||
"lockfileEntries": {},
|
||||
"files": [],
|
||||
"artifacts": {}
|
||||
}
|
2049
ui/dist/assets/index-0320fb82.js
vendored
2049
ui/dist/assets/index-0320fb82.js
vendored
File diff suppressed because one or more lines are too long
2051
ui/dist/assets/index-183b5ca2.js
vendored
Normal file
2051
ui/dist/assets/index-183b5ca2.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
ui/dist/assets/index-815d9fe6.css
vendored
1
ui/dist/assets/index-815d9fe6.css
vendored
File diff suppressed because one or more lines are too long
1
ui/dist/assets/index-8d16ea5e.css
vendored
Normal file
1
ui/dist/assets/index-8d16ea5e.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
ui/dist/index.html
vendored
4
ui/dist/index.html
vendored
@ -6,8 +6,8 @@
|
||||
<title>Invite Relay</title>
|
||||
<link rel="icon" href="data:," />
|
||||
<meta name="description" content="Invite Relay UI" />
|
||||
<script type="module" crossorigin src="./assets/index-0320fb82.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index-815d9fe6.css">
|
||||
<script type="module" crossorigin src="./assets/index-183b5ca2.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index-8d16ea5e.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -7,6 +7,9 @@
|
||||
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();
|
||||
@ -33,8 +36,17 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<article class="prose font-sans px-4 py-6 lg:max-w-7xl lg:pt-6 lg:pb-28">
|
||||
<article class="font-sans px-4 py-6 lg:max-w-7xl lg:pt-6 lg:pb-28">
|
||||
<h1>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 Admin View</button
|
||||
>
|
||||
<AdminView />
|
||||
{:else if adminView == false}
|
||||
<div>
|
||||
{#if $userPublickey === undefined}
|
||||
<button
|
||||
@ -45,6 +57,14 @@
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<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 Admin View</button
|
||||
>
|
||||
</div>
|
||||
{#if invitedata.find((p) => $userPublickey == nip19.npubEncode(p.pk))}
|
||||
<div>
|
||||
<h3>Invite Someone</h3>
|
||||
@ -59,4 +79,5 @@
|
||||
<Hieracy {hierarchy} reload={fetchData} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</article>
|
||||
|
43
ui/src/components/AdminView.svelte
Normal file
43
ui/src/components/AdminView.svelte
Normal file
@ -0,0 +1,43 @@
|
||||
<script lang="js">
|
||||
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("RELAY CONNECT ERROR");
|
||||
console.error(err);
|
||||
});
|
||||
relay.on("connect", () => {
|
||||
console.log(relay.url, "connected");
|
||||
});
|
||||
});
|
||||
|
||||
let filter = { kinds: [1984], limit: 250 };
|
||||
let es = await $ndk.fetchEvents(filter/*, relaySet*/);
|
||||
events = Array.from(es);
|
||||
} catch {
|
||||
console.log("error while getting feed", error);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#each events as event}
|
||||
{#if event.relay.url == relayUrl}
|
||||
<ReportEvent {event} />
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
153
ui/src/components/ReportEvent.svelte
Normal file
153
ui/src/components/ReportEvent.svelte
Normal file
@ -0,0 +1,153 @@
|
||||
<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 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 event = new NDKEvent($ndk);
|
||||
event.kind = 20202;
|
||||
event.tags.push(["p", pk]);
|
||||
await event.publish(relaySet).then(() => reload());
|
||||
} 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 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 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
|
||||
class="rounded-lg inline bg-slate-100 p-2 cursor-pointer hover:bg-white"
|
||||
>
|
||||
View in invite tree
|
||||
</button>
|
||||
<button
|
||||
on:click={() => (show = false)}
|
||||
class="rounded-lg inline bg-green-500 p-2 cursor-pointer hover:bg-green-400"
|
||||
>
|
||||
Dismiss <!-- TO BE IMPLEMENTED: Remove 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 this user
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
@ -25,7 +25,7 @@
|
||||
let confirmation = confirm(
|
||||
`Are you sure you want to remove ${
|
||||
username ? username : person.pk
|
||||
}? All people they invited will also be removed.`,
|
||||
}? 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 {
|
||||
@ -34,8 +34,7 @@
|
||||
const relaySet = new NDKRelaySet(specificRelay, $ndk);
|
||||
relaySet.relays.forEach(async (relay) => {
|
||||
await relay.connect().catch((err) => {
|
||||
console.log("RELAY CONNECT ERROR");
|
||||
console.error(err);
|
||||
console.log("error while connecting to relay", err);
|
||||
});
|
||||
relay.on("connect", () => {
|
||||
console.log("connected");
|
||||
|
21
utils.go
21
utils.go
@ -1,5 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
func isPkInWhitelist(targetPk string) bool {
|
||||
for i := 0; i < len(whitelist); i++ {
|
||||
if whitelist[i].Pk == targetPk {
|
||||
@ -13,6 +19,7 @@ func deleteFromWhitelistRecursively (target string) {
|
||||
var updatedWhitelist []User
|
||||
var queue []string
|
||||
|
||||
// Remove from whitelist
|
||||
for _, user := range whitelist {
|
||||
if user.Pk != target {
|
||||
updatedWhitelist = append(updatedWhitelist, user)
|
||||
@ -21,8 +28,20 @@ func deleteFromWhitelistRecursively (target string) {
|
||||
queue = append(queue, user.Pk);
|
||||
}
|
||||
}
|
||||
|
||||
whitelist = updatedWhitelist
|
||||
|
||||
// Remove all events
|
||||
go func () {
|
||||
var filter nostr.Filter = nostr.Filter{
|
||||
Authors: []string{target},
|
||||
}
|
||||
events, _ := db.QueryEvents(context.TODO(), filter)
|
||||
for ev := range events {
|
||||
db.DeleteEvent(context.TODO(), ev)
|
||||
}
|
||||
}()
|
||||
|
||||
// Recursive
|
||||
for _, pk := range queue {
|
||||
deleteFromWhitelistRecursively(pk)
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ func whitelistRejecter(ctx context.Context, evt *nostr.Event) (reject bool, msg
|
||||
pTags := evt.Tags.GetAll([]string{"p"})
|
||||
for _, tag := range pTags {
|
||||
for _, user := range whitelist {
|
||||
if user.Pk == tag.Value() && user.InvitedBy == evt.PubKey {
|
||||
if user.Pk == tag.Value() && (user.InvitedBy == evt.PubKey || evt.PubKey == relayMaster) {
|
||||
deleteFromWhitelistRecursively(tag.Value())
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user