mirror of
https://github.com/0ceanSlim/grain.git
synced 2024-11-23 17:07:13 +00:00
Compare commits
2 Commits
2ef7d4fe42
...
8d66e3decc
Author | SHA1 | Date | |
---|---|---|---|
8d66e3decc | |||
df361ea437 |
162
app/src/api/importEvents.go
Normal file
162
app/src/api/importEvents.go
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"grain/server/db"
|
||||||
|
relay "grain/server/types"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"golang.org/x/net/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResultData struct {
|
||||||
|
Success bool
|
||||||
|
Message string
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportEvents(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey := r.FormValue("pubkey")
|
||||||
|
relayUrls := r.FormValue("relayUrls")
|
||||||
|
urls := strings.Split(relayUrls, ",")
|
||||||
|
|
||||||
|
var totalEvents int
|
||||||
|
var errorMessage string
|
||||||
|
|
||||||
|
for _, url := range urls {
|
||||||
|
events, err := fetchEventsFromRelay(pubkey, url)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error fetching events from relay %s: %v", url, err)
|
||||||
|
errorMessage = fmt.Sprintf("Error fetching events from relay %s", url)
|
||||||
|
renderResult(w, false, errorMessage, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = storeEvents(events)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error storing events: %v", err)
|
||||||
|
errorMessage = "Error storing events"
|
||||||
|
renderResult(w, false, errorMessage, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
totalEvents += len(events)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderResult(w, true, "Events imported successfully", totalEvents)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderResult(w http.ResponseWriter, success bool, message string, count int) {
|
||||||
|
tmpl, err := template.New("result").Parse(`
|
||||||
|
{{ if .Success }}
|
||||||
|
<p class="text-green-500">Successfully inserted {{ .Count }} events.</p>
|
||||||
|
{{ else }}
|
||||||
|
<p class="text-red-500">Failed to import events: {{ .Message }}</p>
|
||||||
|
{{ end }}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error generating result", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := ResultData{
|
||||||
|
Success: success,
|
||||||
|
Message: message,
|
||||||
|
Count: count,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tmpl.Execute(w, data); err != nil {
|
||||||
|
http.Error(w, "Error rendering result", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchEventsFromRelay(pubkey, relayUrl string) ([]relay.Event, error) {
|
||||||
|
log.Printf("Connecting to relay: %s", relayUrl)
|
||||||
|
conn, err := websocket.Dial(relayUrl, "", "http://localhost/")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error connecting to relay %s: %v", relayUrl, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
log.Printf("Connected to relay: %s", relayUrl)
|
||||||
|
|
||||||
|
reqMessage := fmt.Sprintf(`["REQ", "import-sub", {"authors": ["%s"]}]`, pubkey)
|
||||||
|
log.Printf("Sending request: %s", reqMessage)
|
||||||
|
if _, err := conn.Write([]byte(reqMessage)); err != nil {
|
||||||
|
log.Printf("Error sending request to relay %s: %v", relayUrl, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var events []relay.Event
|
||||||
|
for {
|
||||||
|
var msg []byte
|
||||||
|
if err := websocket.Message.Receive(conn, &msg); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Printf("Error receiving message from relay %s: %v", relayUrl, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Received message: %s", string(msg))
|
||||||
|
|
||||||
|
var response []interface{}
|
||||||
|
if err := json.Unmarshal(msg, &response); err != nil {
|
||||||
|
log.Printf("Error unmarshaling message from relay %s: %v", relayUrl, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if response[0] == "EOSE" {
|
||||||
|
log.Printf("Received EOSE message from relay %s, closing connection", relayUrl)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if response[0] == "EVENT" {
|
||||||
|
eventData, err := json.Marshal(response[2]) // Change index from 1 to 2
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error marshaling event data from relay %s: %v", relayUrl, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var event relay.Event
|
||||||
|
if err := json.Unmarshal(eventData, &event); err != nil {
|
||||||
|
log.Printf("Error unmarshaling event data from relay %s: %v", relayUrl, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
events = append(events, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Fetched %d events from relay %s", len(events), relayUrl)
|
||||||
|
return events, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeEvents(events []relay.Event) error {
|
||||||
|
for _, event := range events {
|
||||||
|
collection := db.GetCollection(event.Kind)
|
||||||
|
_, err := collection.InsertOne(context.TODO(), event)
|
||||||
|
if err != nil {
|
||||||
|
if mongo.IsDuplicateKeyError(err) {
|
||||||
|
log.Printf("Duplicate event ID: %s for event kind: %d", event.ID, event.Kind)
|
||||||
|
} else {
|
||||||
|
log.Printf("Error inserting event with ID: %s for event kind: %d: %v", event.ID, event.Kind, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("Successfully inserted event with ID: %s for event kind: %d", event.ID, event.Kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,15 +1,10 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"grain/server/db"
|
"grain/server/db"
|
||||||
relay "grain/server/types"
|
relay "grain/server/types"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PageData struct {
|
type PageData struct {
|
||||||
@ -78,38 +73,4 @@ func PrependDir(dir string, files []string) []string {
|
|||||||
return fullPaths
|
return fullPaths
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchTopTenRecentEvents queries the database and returns the top ten most recent events.
|
|
||||||
func FetchTopTenRecentEvents(client *mongo.Client) ([]relay.Event, error) {
|
|
||||||
var results []relay.Event
|
|
||||||
|
|
||||||
collections, err := client.Database("grain").ListCollectionNames(context.TODO(), bson.M{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, collectionName := range collections {
|
|
||||||
collection := client.Database("grain").Collection(collectionName)
|
|
||||||
filter := bson.D{}
|
|
||||||
opts := options.Find().SetSort(bson.D{{Key: "createdat", Value: -1}}).SetLimit(10)
|
|
||||||
|
|
||||||
cursor, err := collection.Find(context.TODO(), filter, opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer cursor.Close(context.TODO())
|
|
||||||
|
|
||||||
for cursor.Next(context.TODO()) {
|
|
||||||
var event relay.Event
|
|
||||||
if err := cursor.Decode(&event); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
results = append(results, event)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cursor.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
|
47
app/src/fetchRecentEvents.go
Normal file
47
app/src/fetchRecentEvents.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
relay "grain/server/types"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FetchTopTenRecentEvents queries the database and returns the top ten most recent events.
|
||||||
|
func FetchTopTenRecentEvents(client *mongo.Client) ([]relay.Event, error) {
|
||||||
|
var results []relay.Event
|
||||||
|
|
||||||
|
collections, err := client.Database("grain").ListCollectionNames(context.TODO(), bson.M{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, collectionName := range collections {
|
||||||
|
collection := client.Database("grain").Collection(collectionName)
|
||||||
|
filter := bson.D{}
|
||||||
|
opts := options.Find().SetSort(bson.D{{Key: "createdat", Value: -1}}).SetLimit(10)
|
||||||
|
|
||||||
|
cursor, err := collection.Find(context.TODO(), filter, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close(context.TODO())
|
||||||
|
|
||||||
|
for cursor.Next(context.TODO()) {
|
||||||
|
var event relay.Event
|
||||||
|
if err := cursor.Decode(&event); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
results = append(results, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cursor.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
15
app/src/routes/importEvents.go
Normal file
15
app/src/routes/importEvents.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
app "grain/app/src"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ImportEvents(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := app.PageData{
|
||||||
|
Title: "Import Events",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call RenderTemplate with the specific template for this route
|
||||||
|
app.RenderTemplate(w, data, "importEvents.html")
|
||||||
|
}
|
53
app/views/importEvents.html
Normal file
53
app/views/importEvents.html
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
{{define "view"}}
|
||||||
|
<main class="flex flex-col items-center justify-center p-8">
|
||||||
|
<div class="mb-4">You are now viewing the {{.Title}}</div>
|
||||||
|
<div
|
||||||
|
class="container flex justify-center p-4 border-2 border-gray-600 border-solid rounded-md w-fit"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
id="import-form"
|
||||||
|
hx-post="/import-results"
|
||||||
|
hx-target="#result"
|
||||||
|
hx-indicator="#spinner"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<label for="pubkey">Pubkey:</label>
|
||||||
|
<input
|
||||||
|
class="p-2 m-2 text-black rounded-md"
|
||||||
|
type="text"
|
||||||
|
id="pubkey"
|
||||||
|
name="pubkey"
|
||||||
|
required
|
||||||
|
maxlength="64"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="relayUrls">Relay URLs (comma separated):</label>
|
||||||
|
<input
|
||||||
|
class="p-2 m-2 text-black rounded-md"
|
||||||
|
type="text"
|
||||||
|
id="relayUrls"
|
||||||
|
name="relayUrls"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="p-2 m-2 font-bold bg-green-500 rounded-md font-xl"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Import Events
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="spinner" class="spinner"></div>
|
||||||
|
<div id="result" class="p-2 m-2 text-xl font-bold"></div>
|
||||||
|
<button
|
||||||
|
hx-get="/"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-target="body"
|
||||||
|
class="p-2 m-2 text-white bg-blue-400 rounded-md"
|
||||||
|
>
|
||||||
|
Return to Dashboard
|
||||||
|
</button>
|
||||||
|
</main>
|
||||||
|
{{end}}
|
@ -1,7 +1,7 @@
|
|||||||
{{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">You are now viewing the {{.Title}}</div>
|
<div class="mb-4">You are now viewing the {{.Title}}</div>
|
||||||
<h2>Top Ten Most Recent Events</h2>
|
<!--<h2>Top Ten Most Recent Events</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{{range .Events}}
|
{{range .Events}}
|
||||||
<li>
|
<li>
|
||||||
@ -11,6 +11,14 @@
|
|||||||
<strong>PubKey:</strong> {{.PubKey}}
|
<strong>PubKey:</strong> {{.PubKey}}
|
||||||
</li>
|
</li>
|
||||||
{{end}}
|
{{end}}
|
||||||
</ul>
|
</ul>-->
|
||||||
|
<button
|
||||||
|
hx-get="/import-events"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
class="p-2 mb-2 text-white bg-orange-400 rounded-md"
|
||||||
|
hx-target="body"
|
||||||
|
>
|
||||||
|
Import Events
|
||||||
|
</button>
|
||||||
</main>
|
</main>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -26,6 +26,26 @@
|
|||||||
<link href="/static/custom.min.css" rel="stylesheet" />
|
<link href="/static/custom.min.css" rel="stylesheet" />
|
||||||
<link rel="icon" href="/static/img/favicon.ico" type="image/x-icon" />
|
<link rel="icon" href="/static/img/favicon.ico" type="image/x-icon" />
|
||||||
<title>{{.Title}}</title>
|
<title>{{.Title}}</title>
|
||||||
|
<style>
|
||||||
|
.spinner {
|
||||||
|
display: none;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border: 5px solid lightgray;
|
||||||
|
border-top: 5px solid blue;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="font-mono text-center text-textPrimary bg-bgPrimary">
|
<body class="font-mono text-center text-textPrimary bg-bgPrimary">
|
||||||
{{template "header" .}} {{template "view" .}} {{template "footer" .}}
|
{{template "header" .}} {{template "view" .}} {{template "footer" .}}
|
||||||
|
6
main.go
6
main.go
@ -2,7 +2,9 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"grain/app"
|
app "grain/app/src"
|
||||||
|
"grain/app/src/api"
|
||||||
|
"grain/app/src/routes"
|
||||||
"grain/config"
|
"grain/config"
|
||||||
configTypes "grain/config/types"
|
configTypes "grain/config/types"
|
||||||
relay "grain/server"
|
relay "grain/server"
|
||||||
@ -47,6 +49,8 @@ func main() {
|
|||||||
func setupRoutes() *http.ServeMux {
|
func setupRoutes() *http.ServeMux {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/", ListenAndServe)
|
mux.HandleFunc("/", ListenAndServe)
|
||||||
|
mux.HandleFunc("/import-results", api.ImportEvents)
|
||||||
|
mux.HandleFunc("/import-events", routes.ImportEvents)
|
||||||
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("app/static"))))
|
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("app/static"))))
|
||||||
mux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.ServeFile(w, r, "app/static/img/favicon.ico")
|
http.ServeFile(w, r, "app/static/img/favicon.ico")
|
||||||
|
Loading…
Reference in New Issue
Block a user