Compare commits

..

No commits in common. "7edbee13ac9dc7b8589d6fbb604d3b45f61a151c" and "a2f7f0a5b4c5f81d7c053bcd26b53327324df491" have entirely different histories.

8 changed files with 104 additions and 165 deletions

View File

@ -40,30 +40,19 @@ func ImportEvents(w http.ResponseWriter, r *http.Request) {
for _, url := range urls { for _, url := range urls {
var events []map[string]interface{} var events []map[string]interface{}
var lastEventCreatedAt int64 = 0 // Track the timestamp of the last event fetched events, err = fetchEventsFromRelay(pubkey, url)
if err != nil {
for { errorChan <- fmt.Errorf("error fetching events from relay %s: %w", url, err)
events, err = fetchEventsFromRelay(pubkey, url, lastEventCreatedAt) return
if err != nil {
errorChan <- fmt.Errorf("error fetching events from relay %s: %w", url, err)
return
}
if len(events) == 0 {
break
}
err = sendEventsToRelay(events)
if err != nil {
errorChan <- fmt.Errorf("error sending events to relay: %w", err)
return
}
totalEvents += len(events)
// Update lastEventCreatedAt with the timestamp of the last event fetched
lastEventCreatedAt = int64(events[len(events)-1]["created_at"].(float64))
} }
err = sendEventsToRelay(events)
if err != nil {
errorChan <- fmt.Errorf("error sending events to relay: %w", err)
return
}
totalEvents += len(events)
} }
totalEventsChan <- totalEvents totalEventsChan <- totalEvents
@ -74,7 +63,7 @@ func ImportEvents(w http.ResponseWriter, r *http.Request) {
renderResult(w, true, "Events imported successfully", totalEvents) renderResult(w, true, "Events imported successfully", totalEvents)
case err := <-errorChan: case err := <-errorChan:
renderResult(w, false, err.Error(), 0) renderResult(w, false, err.Error(), 0)
case <-time.After(10 * time.Minute): // Increase timeout for large imports case <-time.After(5 * time.Minute):
renderResult(w, false, "Timeout importing events", 0) renderResult(w, false, "Timeout importing events", 0)
} }
} }
@ -103,7 +92,7 @@ func renderResult(w http.ResponseWriter, success bool, message string, count int
} }
} }
func fetchEventsFromRelay(pubkey, relayUrl string, lastEventCreatedAt int64) ([]map[string]interface{}, error) { func fetchEventsFromRelay(pubkey, relayUrl string) ([]map[string]interface{}, error) {
log.Printf("Connecting to relay: %s", relayUrl) log.Printf("Connecting to relay: %s", relayUrl)
conn, err := websocket.Dial(relayUrl, "", "http://localhost/") conn, err := websocket.Dial(relayUrl, "", "http://localhost/")
if err != nil { if err != nil {
@ -113,17 +102,7 @@ func fetchEventsFromRelay(pubkey, relayUrl string, lastEventCreatedAt int64) ([]
defer conn.Close() defer conn.Close()
log.Printf("Connected to relay: %s", relayUrl) log.Printf("Connected to relay: %s", relayUrl)
filters := map[string]interface{}{ reqMessage := fmt.Sprintf(`["REQ", "import-sub", {"authors": ["%s"]}]`, pubkey)
"authors": []string{pubkey},
"limit": 100,
}
if lastEventCreatedAt > 0 {
filters["until"] = lastEventCreatedAt - 1
}
filtersJSON, _ := json.Marshal(filters)
reqMessage := fmt.Sprintf(`["REQ", "import-sub", %s]`, filtersJSON)
log.Printf("Sending request: %s", reqMessage) log.Printf("Sending request: %s", reqMessage)
if _, err := conn.Write([]byte(reqMessage)); err != nil { if _, err := conn.Write([]byte(reqMessage)); err != nil {
log.Printf("Error sending request to relay %s: %v", relayUrl, err) log.Printf("Error sending request to relay %s: %v", relayUrl, err)
@ -177,7 +156,7 @@ func sendEventsToRelay(events []map[string]interface{}) error {
relayUrl := fmt.Sprintf("ws://localhost%s", cfg.Server.Port) relayUrl := fmt.Sprintf("ws://localhost%s", cfg.Server.Port)
batchSize := 20 // Reduce the batch size to avoid connection issues batchSize := 5 // Reduce the batch size to avoid connection issues
for i := 0; i < len(events); i += batchSize { for i := 0; i < len(events); i += batchSize {
end := i + batchSize end := i + batchSize
if end > len(events) { if end > len(events) {
@ -188,9 +167,6 @@ func sendEventsToRelay(events []map[string]interface{}) error {
if err := sendBatchToRelay(batch, relayUrl); err != nil { if err := sendBatchToRelay(batch, relayUrl); err != nil {
return err return err
} }
// Wait for a short period to avoid overloading the relay server
time.Sleep(1 * time.Second)
} }
return nil return nil
@ -220,8 +196,9 @@ func sendBatchToRelay(events []map[string]interface{}, relayUrl string) error {
} }
log.Printf("Sent event to local relay: %s", event["id"]) log.Printf("Sent event to local relay: %s", event["id"])
} }
// Wait for a short period to avoid overloading the relay server // Wait for a short period to avoid overloading the relay server
time.Sleep(1 * time.Second) time.Sleep(100 * time.Millisecond)
return nil return nil
} }

View File

@ -23,7 +23,7 @@ func RootHandler(w http.ResponseWriter, r *http.Request) {
} }
data := PageData{ data := PageData{
Title: "GRAIN Dashboard", Title: "GRAIN Relay",
Events: events, Events: events,
} }

View File

@ -1,6 +1,6 @@
{{define "view"}} {{define "view"}}
<main class="flex flex-col items-center justify-center p-8"> <main class="flex flex-col items-center justify-center p-8">
<div class="mb-4">{{.Title}}</div> <div class="mb-4">You are now viewing the {{.Title}}</div>
<div <div
class="container flex justify-center p-4 border-2 border-gray-600 border-solid rounded-md w-fit" class="container flex justify-center p-4 border-2 border-gray-600 border-solid rounded-md w-fit"
> >
@ -10,28 +10,26 @@
hx-target="#result" hx-target="#result"
hx-indicator="#spinner" hx-indicator="#spinner"
> >
<div class="content-right"> <div>
<div> <label for="pubkey">Pubkey:</label>
<label for="pubkey">Pubkey:</label> <input
<input class="p-2 m-2 text-black rounded-md"
class="p-2 m-2 text-black rounded-md" type="text"
type="text" id="pubkey"
id="pubkey" name="pubkey"
name="pubkey" required
required maxlength="64"
maxlength="64" />
/> </div>
</div> <div>
<div> <label for="relayUrls">Relay URLs (comma separated):</label>
<label for="relayUrls">Relay URLs (comma separated):</label> <input
<input class="p-2 m-2 text-black rounded-md"
class="p-2 m-2 text-black rounded-md" type="text"
type="text" id="relayUrls"
id="relayUrls" name="relayUrls"
name="relayUrls" required
required />
/>
</div>
</div> </div>
<button <button
class="p-2 m-2 font-bold bg-green-500 rounded-md font-xl" class="p-2 m-2 font-bold bg-green-500 rounded-md font-xl"
@ -39,18 +37,9 @@
> >
Import Events Import Events
</button> </button>
<div class="font-bold text-md">
⚠️ This Feature is Experimental<br />
If you are whitelisted, this SHOULD capture all of your events<br />
Please Be Patient. Imports can take quite some time due to Rate Limits
</div>
</form> </form>
</div> </div>
<div <div id="spinner" class="spinner"></div>
id="spinner"
class="m-4 text-lg font-bold text-center spinner"
style="display: none"
></div>
<div id="result" class="p-2 m-2 text-xl font-bold"></div> <div id="result" class="p-2 m-2 text-xl font-bold"></div>
<button <button
hx-get="/" hx-get="/"
@ -67,13 +56,11 @@
document.getElementById("spinner").style.display = "block"; document.getElementById("spinner").style.display = "block";
}); });
document.addEventListener("htmx:afterRequest", function () { document
document.getElementById("spinner").style.display = "none"; .getElementById("result")
}); .addEventListener("htmx:afterRequest", function () {
document.getElementById("spinner").style.display = "none";
document.addEventListener("htmx:requestError", function () { });
document.getElementById("spinner").style.display = "none";
});
</script> </script>
</main> </main>
{{end}} {{end}}

View File

@ -15,7 +15,7 @@
<button <button
hx-get="/import-events" hx-get="/import-events"
hx-swap="outerHTML" hx-swap="outerHTML"
class="p-2 mb-2 text-white bg-purple-500 rounded-md" class="p-2 mb-2 text-white bg-orange-400 rounded-md"
hx-target="body" hx-target="body"
> >
Import Events Import Events

View File

@ -1,23 +1,5 @@
{{define "footer"}} {{define "footer"}}
<footer class="text-textMuted"> <footer class="text-textMuted">
<p> <p>&copy; 2024 GRAIN 🌾, made with 💜 by OceanSlim</p>
&copy; 2024
<a href="https://github.com/0ceanSlim/grain" class="text-purple-400"
>GRAIN 🌾</a
>, made with 💜 by
<a
href="https://njump.me/npub1zmc6qyqdfnllhnzzxr5wpepfpnzcf8q6m3jdveflmgruqvd3qa9sjv7f60"
class="text-purple-400"
>OceanSlim</a
>
</p>
<p>
GRAIN is Proudly Open Source. Don't hesitate to
<a
href="https://github.com/0ceanSlim/grain?tab=readme-ov-file#development"
class="text-purple-400"
>contribute</a
>!
</p>
</footer> </footer>
{{end}} {{end}}

View File

@ -29,10 +29,10 @@
<style> <style>
.spinner { .spinner {
display: none; display: none;
width: 32px; width: 50px;
height: 32px; height: 50px;
border: 5px solid purple; border: 5px solid lightgray;
border-top: 5px solid violet; border-top: 5px solid blue;
border-radius: 50%; border-radius: 50%;
animation: spin 1s linear infinite; animation: spin 1s linear infinite;
} }

View File

@ -9,6 +9,7 @@ import (
"grain/server/handlers/kinds" "grain/server/handlers/kinds"
"grain/server/handlers/response" "grain/server/handlers/response"
"grain/server/utils" "grain/server/utils"
"strconv"
relay "grain/server/types" relay "grain/server/types"
@ -73,13 +74,13 @@ func HandleKind(ctx context.Context, evt relay.Event, ws *websocket.Conn, eventS
} }
// Check if the kind is whitelisted // Check if the kind is whitelisted
if config.GetConfig().KindWhitelist.Enabled && !utils.IsKindWhitelisted(evt.Kind) { if config.GetConfig().KindWhitelist.Enabled && !isKindWhitelisted(evt.Kind) {
response.SendOK(ws, evt.ID, false, "not allowed: event kind is not whitelisted") response.SendOK(ws, evt.ID, false, "not allowed: event kind is not whitelisted")
return return
} }
// Check pubkey/npub whitelist only if the kind is not whitelisted // Check pubkey/npub whitelist only if the kind is not whitelisted
if config.GetConfig().PubkeyWhitelist.Enabled && !utils.IsPubKeyWhitelisted(evt.PubKey) { if config.GetConfig().PubkeyWhitelist.Enabled && !isPubKeyWhitelisted(evt.PubKey) {
response.SendOK(ws, evt.ID, false, "not allowed: pubkey or npub is not whitelisted") response.SendOK(ws, evt.ID, false, "not allowed: pubkey or npub is not whitelisted")
return return
} }
@ -147,3 +148,52 @@ func determineCategory(kind int) string {
} }
} }
// Helper function to check if a pubkey or npub is whitelisted
func isPubKeyWhitelisted(pubKey string) bool {
cfg := config.GetConfig()
if !cfg.PubkeyWhitelist.Enabled {
return true
}
// Check pubkeys
for _, whitelistedKey := range cfg.PubkeyWhitelist.Pubkeys {
if pubKey == whitelistedKey {
return true
}
}
// Check npubs
for _, npub := range cfg.PubkeyWhitelist.Npubs {
decodedPubKey, err := utils.DecodeNpub(npub)
if err != nil {
fmt.Println("Error decoding npub:", err)
continue
}
if pubKey == decodedPubKey {
return true
}
}
return false
}
func isKindWhitelisted(kind int) bool {
cfg := config.GetConfig()
if !cfg.KindWhitelist.Enabled {
return true
}
// Check event kinds
for _, whitelistedKindStr := range cfg.KindWhitelist.Kinds {
whitelistedKind, err := strconv.Atoi(whitelistedKindStr)
if err != nil {
fmt.Println("Error converting whitelisted kind to int:", err)
continue
}
if kind == whitelistedKind {
return true
}
}
return false
}

View File

@ -1,57 +0,0 @@
package utils
import (
"fmt"
"grain/config"
"strconv"
)
// Helper function to check if a pubkey or npub is whitelisted
func IsPubKeyWhitelisted(pubKey string) bool {
cfg := config.GetConfig()
if !cfg.PubkeyWhitelist.Enabled {
return true
}
// Check pubkeys
for _, whitelistedKey := range cfg.PubkeyWhitelist.Pubkeys {
if pubKey == whitelistedKey {
return true
}
}
// Check npubs
for _, npub := range cfg.PubkeyWhitelist.Npubs {
decodedPubKey, err := DecodeNpub(npub)
if err != nil {
fmt.Println("Error decoding npub:", err)
continue
}
if pubKey == decodedPubKey {
return true
}
}
return false
}
func IsKindWhitelisted(kind int) bool {
cfg := config.GetConfig()
if !cfg.KindWhitelist.Enabled {
return true
}
// Check event kinds
for _, whitelistedKindStr := range cfg.KindWhitelist.Kinds {
whitelistedKind, err := strconv.Atoi(whitelistedKindStr)
if err != nil {
fmt.Println("Error converting whitelisted kind to int:", err)
continue
}
if kind == whitelistedKind {
return true
}
}
return false
}