mirror of
https://github.com/0ceanSlim/grain.git
synced 2024-11-22 08:37:13 +00:00
Merge pull request 'signature_verification' (#1) from signature_verification into main
Reviewed-on: https://forge.happytavern.co/oceanslim/grain/pulls/1
This commit is contained in:
commit
51a7fd7924
162
events/events.go
162
events/events.go
@ -2,59 +2,165 @@ package events
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
"golang.org/x/net/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
PubKey string `json:"pubkey"`
|
PubKey string `json:"pubkey"`
|
||||||
CreatedAt int64 `json:"created_at"`
|
CreatedAt int64 `json:"created_at"`
|
||||||
Kind int `json:"kind"`
|
Kind int `json:"kind"`
|
||||||
Tags []string `json:"tags"`
|
Tags [][]string `json:"tags"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Sig string `json:"sig"`
|
Sig string `json:"sig"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var eventKind0Collection *mongo.Collection
|
var collections = make(map[int]*mongo.Collection)
|
||||||
var eventKind1Collection *mongo.Collection
|
|
||||||
|
|
||||||
func InitCollections(client *mongo.Client, eventKind0, eventKind1 string) {
|
func InitCollections(client *mongo.Client, kinds ...int) {
|
||||||
eventKind0Collection = client.Database("grain").Collection(eventKind0)
|
for _, kind := range kinds {
|
||||||
eventKind1Collection = client.Database("grain").Collection(eventKind1)
|
collectionName := fmt.Sprintf("event-kind%d", kind)
|
||||||
|
collections[kind] = client.Database("grain").Collection(collectionName)
|
||||||
|
indexModel := mongo.IndexModel{
|
||||||
|
Keys: bson.D{{Key: "id", Value: 1}},
|
||||||
|
Options: options.Index().SetUnique(true),
|
||||||
|
}
|
||||||
|
_, err := collections[kind].Indexes().CreateOne(context.TODO(), indexModel)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to create index on %s: %v\n", collectionName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCollection(kind int, client *mongo.Client) *mongo.Collection {
|
||||||
|
if collection, exists := collections[kind]; exists {
|
||||||
|
return collection
|
||||||
|
}
|
||||||
|
collectionName := fmt.Sprintf("event-kind%d", kind)
|
||||||
|
collection := client.Database("grain").Collection(collectionName)
|
||||||
|
collections[kind] = collection
|
||||||
indexModel := mongo.IndexModel{
|
indexModel := mongo.IndexModel{
|
||||||
Keys: bson.D{{Key: "id", Value: 1}},
|
Keys: bson.D{{Key: "id", Value: 1}},
|
||||||
Options: options.Index().SetUnique(true),
|
Options: options.Index().SetUnique(true),
|
||||||
}
|
}
|
||||||
_, err := eventKind0Collection.Indexes().CreateOne(context.TODO(), indexModel)
|
_, err := collection.Indexes().CreateOne(context.TODO(), indexModel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to create index on event-kind0: ", err)
|
fmt.Printf("Failed to create index on %s: %v\n", collectionName, err)
|
||||||
}
|
|
||||||
_, err = eventKind1Collection.Indexes().CreateOne(context.TODO(), indexModel)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Failed to create index on event-kind1: ", err)
|
|
||||||
}
|
}
|
||||||
|
return collection
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleEvent(ctx context.Context, evt Event) error {
|
func HandleEvent(ctx context.Context, evt Event, client *mongo.Client, ws *websocket.Conn) {
|
||||||
|
if !ValidateEvent(evt) {
|
||||||
|
sendOKResponse(ws, evt.ID, false, "invalid: signature verification failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
collection := GetCollection(evt.Kind, client)
|
||||||
|
|
||||||
|
var err error
|
||||||
switch evt.Kind {
|
switch evt.Kind {
|
||||||
case 0:
|
case 0:
|
||||||
return HandleEventKind0(ctx, evt, eventKind0Collection)
|
err = HandleEventKind0(ctx, evt, collection)
|
||||||
case 1:
|
case 1:
|
||||||
return HandleEventKind1(ctx, evt, eventKind1Collection)
|
err = HandleEventKind1(ctx, evt, collection)
|
||||||
default:
|
default:
|
||||||
fmt.Println("Unknown event kind:", evt.Kind)
|
err = HandleDefaultEvent(ctx, evt, collection)
|
||||||
return fmt.Errorf("unknown event kind: %d", evt.Kind)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
sendOKResponse(ws, evt.ID, false, fmt.Sprintf("error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sendOKResponse(ws, evt.ID, true, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCollections() map[string]*mongo.Collection {
|
func sendOKResponse(ws *websocket.Conn, eventID string, status bool, message string) {
|
||||||
return map[string]*mongo.Collection{
|
response := []interface{}{"OK", eventID, status, message}
|
||||||
"eventKind0": eventKind0Collection,
|
responseBytes, _ := json.Marshal(response)
|
||||||
"eventKind1": eventKind1Collection,
|
websocket.Message.Send(ws, string(responseBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SerializeEvent(evt Event) []byte {
|
||||||
|
eventData := []interface{}{
|
||||||
|
0,
|
||||||
|
evt.PubKey,
|
||||||
|
evt.CreatedAt,
|
||||||
|
evt.Kind,
|
||||||
|
evt.Tags,
|
||||||
|
evt.Content,
|
||||||
|
}
|
||||||
|
serializedEvent, _ := json.Marshal(eventData)
|
||||||
|
return serializedEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func ValidateEvent(evt Event) bool {
|
||||||
|
serializedEvent := SerializeEvent(evt)
|
||||||
|
hash := sha256.Sum256(serializedEvent)
|
||||||
|
eventID := hex.EncodeToString(hash[:])
|
||||||
|
if eventID != evt.ID {
|
||||||
|
log.Printf("Invalid ID: expected %s, got %s\n", eventID, evt.ID)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sigBytes, err := hex.DecodeString(evt.Sig)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error decoding signature: %v\n", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := schnorr.ParseSignature(sigBytes)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error parsing signature: %v\n", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKeyBytes, err := hex.DecodeString(evt.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error decoding public key: %v\n", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var pubKey *btcec.PublicKey
|
||||||
|
if len(pubKeyBytes) == 32 {
|
||||||
|
// Handle 32-byte public key (x-coordinate only)
|
||||||
|
pubKey, err = btcec.ParsePubKey(append([]byte{0x02}, pubKeyBytes...))
|
||||||
|
} else {
|
||||||
|
// Handle standard compressed or uncompressed public key
|
||||||
|
pubKey, err = btcec.ParsePubKey(pubKeyBytes)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error parsing public key: %v\n", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
verified := sig.Verify(hash[:], pubKey)
|
||||||
|
if !verified {
|
||||||
|
log.Printf("Signature verification failed for event ID: %s\n", evt.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return verified
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleDefaultEvent(ctx context.Context, evt Event, collection *mongo.Collection) error {
|
||||||
|
_, err := collection.InsertOne(ctx, evt)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error inserting default event into MongoDB: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Inserted default event into MongoDB:", evt.ID)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func HandleEventKind0(ctx context.Context, evt Event, collection *mongo.Collection) error {
|
func HandleEventKind0(ctx context.Context, evt Event, collection *mongo.Collection) error {
|
||||||
// Perform specific validation for event kind 0
|
|
||||||
if !isValidEventKind0(evt) {
|
|
||||||
return fmt.Errorf("validation failed for event kind 0: %s", evt.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the existing event if it has the same pubkey
|
// Replace the existing event if it has the same pubkey
|
||||||
filter := bson.M{"pubkey": evt.PubKey}
|
filter := bson.M{"pubkey": evt.PubKey}
|
||||||
update := bson.M{
|
update := bson.M{
|
||||||
@ -28,21 +23,12 @@ func HandleEventKind0(ctx context.Context, evt Event, collection *mongo.Collecti
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
options := options.Update().SetUpsert(true) // Insert if not found
|
opts := options.Update().SetUpsert(true) // Insert if not found
|
||||||
_, err := collection.UpdateOne(ctx, filter, update, options)
|
_, err := collection.UpdateOne(ctx, filter, update, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error updating/inserting event kind 0 into MongoDB:", err)
|
return fmt.Errorf("Error updating/inserting event kind 0 into MongoDB: %v", err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Upserted event kind 0 into MongoDB:", evt.ID)
|
fmt.Println("Upserted event kind 0 into MongoDB:", evt.ID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isValidEventKind0(evt Event) bool {
|
|
||||||
// Placeholder for actual validation logic
|
|
||||||
if evt.Content == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
@ -8,26 +8,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func HandleEventKind1(ctx context.Context, evt Event, collection *mongo.Collection) error {
|
func HandleEventKind1(ctx context.Context, evt Event, collection *mongo.Collection) error {
|
||||||
// Perform specific validation for event kind 1
|
|
||||||
if !isValidEventKind1(evt) {
|
|
||||||
return fmt.Errorf("validation failed for event kind 1: %s", evt.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert event into MongoDB
|
// Insert event into MongoDB
|
||||||
_, err := collection.InsertOne(ctx, evt)
|
_, err := collection.InsertOne(ctx, evt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error inserting event into MongoDB:", err)
|
return fmt.Errorf("Error inserting event into MongoDB: %v", err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Inserted event kind 1 into MongoDB:", evt.ID)
|
fmt.Println("Inserted event kind 1 into MongoDB:", evt.ID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isValidEventKind1(evt Event) bool {
|
|
||||||
// Placeholder for actual validation logic
|
|
||||||
if evt.Content == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
6
go.mod
6
go.mod
@ -3,11 +3,16 @@ module grain
|
|||||||
go 1.22.2
|
go 1.22.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.3.4
|
||||||
go.mongodb.org/mongo-driver v1.16.0
|
go.mongodb.org/mongo-driver v1.16.0
|
||||||
golang.org/x/net v0.27.0
|
golang.org/x/net v0.27.0
|
||||||
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/klauspost/compress v1.13.6 // indirect
|
github.com/klauspost/compress v1.13.6 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
@ -18,5 +23,4 @@ require (
|
|||||||
golang.org/x/crypto v0.25.0 // indirect
|
golang.org/x/crypto v0.25.0 // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
9
go.sum
9
go.sum
@ -1,5 +1,13 @@
|
|||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
|
||||||
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
|
||||||
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
@ -50,6 +58,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
|||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
2
main.go
2
main.go
@ -28,7 +28,7 @@ func main() {
|
|||||||
defer db.DisconnectDB(client)
|
defer db.DisconnectDB(client)
|
||||||
|
|
||||||
// Initialize collections
|
// Initialize collections
|
||||||
events.InitCollections(client, config.Collections.EventKind0, config.Collections.EventKind1)
|
events.InitCollections(client, 0, 1) // Initialize known kinds
|
||||||
|
|
||||||
server.SetClient(client)
|
server.SetClient(client)
|
||||||
|
|
||||||
|
84
readme.md
84
readme.md
@ -1,63 +1,25 @@
|
|||||||
# GRAIN 🌾 WIP
|
# GRAIN 🌾
|
||||||
|
|
||||||
|
|
||||||
TODO:
|
|
||||||
- Handling kind 0 and kind 1 EVENTS
|
|
||||||
- Validation Checking
|
|
||||||
- updating (replacing replacable notes)
|
|
||||||
- Handle REQs (requests)
|
|
||||||
- Handle CLOSE
|
|
||||||
|
|
||||||
**Go Relay Archetecture for Implementing Nostr**
|
**Go Relay Archetecture for Implementing Nostr**
|
||||||
|
|
||||||
GRAIN is an open-source Nostr relay implementation written in Go. This project aims to provide a robust and efficient Nostr relay that supports the NIP-01 protocol, focusing on processing user metadata and text notes.
|
GRAIN is an open-source Nostr relay implementation written in Go. This project aims to provide a robust and efficient Nostr relay that currently supports the NIP-01 of the nostr protocol.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **NIP-01 Protocol Support**: GRAIN fully supports the NIP-01 protocol for WebSocket communication.
|
- **NIP-01 Protocol Support**: GRAIN fully supports the NIP-01 for WebSocket communication.
|
||||||
- **Event Processing**: Handles events of kind 0 (user metadata) and kind 1 (text note).
|
- **Event Processing**: Handles events of kind 0 (user metadata) and kind 1 (text note).
|
||||||
- **MongoDB 🍃**: Utilizes MongoDB to store and manage events efficiently.
|
- **MongoDB 🍃**: Utilizes MongoDB to store and manage events efficiently.
|
||||||
- **Scalability**: Built with Go, ensuring high performance and scalability.
|
- **Scalability**: Built with Go, ensuring high performance and scalability.
|
||||||
- **Open Source**: Licensed under the MIT License, making it free to use and modify.
|
- **Open Source**: Licensed under the MIT License, making it free to use and modify.
|
||||||
|
|
||||||
## Installation
|
## Configuration 🍃
|
||||||
|
|
||||||
1. **Clone the repository**:
|
Configuration options can be set through environment variables or a configuration file.
|
||||||
|
|
||||||
```sh
|
There is an example config in this repo. Copy the example config to config.yml to get started
|
||||||
git clone https://github.com/oceanslim/grain.git
|
|
||||||
cd grain
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Build the executable**:
|
```bash
|
||||||
|
cp config.example.yml config.yml
|
||||||
```sh
|
|
||||||
go build -o grain.exe
|
|
||||||
```
|
|
||||||
|
|
||||||
The `grain.exe` will be placed in a temporary directory within `...\appdata\local\temp\go-build` and subdirectories.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
To run the GRAIN relay:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./grain.exe
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuration 🍃
|
|
||||||
|
|
||||||
Configuration options can be set through environment variables or a configuration file. Example configuration:
|
|
||||||
|
|
||||||
```yml
|
|
||||||
server:
|
|
||||||
port: 8080
|
|
||||||
database:
|
|
||||||
type: mongodb
|
|
||||||
connection_string: mongodb://localhost:27017
|
|
||||||
database_name: grain
|
|
||||||
logging:
|
|
||||||
level: info
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### WebSocket Endpoints
|
### WebSocket Endpoints
|
||||||
@ -65,33 +27,37 @@ logging:
|
|||||||
- Connect: / - Clients can connect to this endpoint to start a WebSocket session.
|
- Connect: / - Clients can connect to this endpoint to start a WebSocket session.
|
||||||
- Publish Event: Send events of kind 0 (user metadata) or kind 1 (text note) to the relay.
|
- Publish Event: Send events of kind 0 (user metadata) or kind 1 (text note) to the relay.
|
||||||
|
|
||||||
|
### TODO
|
||||||
|
|
||||||
|
- Handle more kinds
|
||||||
|
- create whitelist/blacklist functionality
|
||||||
|
for:
|
||||||
|
- valid nip05 domain
|
||||||
|
- pubkey
|
||||||
|
- npub
|
||||||
|
- kind int
|
||||||
|
- kind 1 wordlist
|
||||||
|
- Rate limit Events
|
||||||
|
|
||||||
### Development
|
### Development
|
||||||
|
|
||||||
To contribute to GRAIN, follow these steps:
|
To contribute to GRAIN, follow these steps:
|
||||||
|
|
||||||
1. Fork the repository.
|
1. Fork the repository.
|
||||||
2. Create a new branch:
|
2. Make your changes.
|
||||||
|
3. Commit your changes:
|
||||||
```sh
|
|
||||||
git checkout -b feature-branch
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Make your changes.
|
|
||||||
4. Commit your changes:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git commit -m "Description of changes"
|
git commit -m "Description of changes"
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Push to the branch:
|
4. Push to the repo:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git push origin feature-branch
|
git push
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Create a Pull Request.
|
5. Create a Pull Request.
|
||||||
|
|
||||||
### Contributing
|
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
|
111
server/server.go
111
server/server.go
@ -7,6 +7,8 @@ import (
|
|||||||
"grain/events"
|
"grain/events"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"grain/utils"
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
"golang.org/x/net/websocket"
|
"golang.org/x/net/websocket"
|
||||||
)
|
)
|
||||||
@ -101,11 +103,7 @@ func handleEvent(ws *websocket.Conn, message []interface{}) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = events.HandleEvent(context.TODO(), evt)
|
events.HandleEvent(context.TODO(), evt, client, ws)
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error handling event:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Event processed:", evt.ID)
|
fmt.Println("Event processed:", evt.ID)
|
||||||
}
|
}
|
||||||
@ -131,13 +129,13 @@ func handleReq(ws *websocket.Conn, message []interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var f Filter
|
var f Filter
|
||||||
f.IDs = toStringArray(filterData["ids"])
|
f.IDs = utils.ToStringArray(filterData["ids"])
|
||||||
f.Authors = toStringArray(filterData["authors"])
|
f.Authors = utils.ToStringArray(filterData["authors"])
|
||||||
f.Kinds = toIntArray(filterData["kinds"])
|
f.Kinds = utils.ToIntArray(filterData["kinds"])
|
||||||
f.Tags = toTagsMap(filterData["tags"])
|
f.Tags = utils.ToTagsMap(filterData["tags"])
|
||||||
f.Since = toTime(filterData["since"])
|
f.Since = utils.ToTime(filterData["since"])
|
||||||
f.Until = toTime(filterData["until"])
|
f.Until = utils.ToTime(filterData["until"])
|
||||||
f.Limit = toInt(filterData["limit"])
|
f.Limit = utils.ToInt(filterData["limit"])
|
||||||
|
|
||||||
filters[i] = f
|
filters[i] = f
|
||||||
}
|
}
|
||||||
@ -195,92 +193,3 @@ func handleClose(ws *websocket.Conn, message []interface{}) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toStringArray(i interface{}) []string {
|
|
||||||
if i == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
arr, ok := i.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var result []string
|
|
||||||
for _, v := range arr {
|
|
||||||
str, ok := v.(string)
|
|
||||||
if ok {
|
|
||||||
result = append(result, str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func toIntArray(i interface{}) []int {
|
|
||||||
if i == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
arr, ok := i.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var result []int
|
|
||||||
for _, v := range arr {
|
|
||||||
num, ok := v.(float64)
|
|
||||||
if ok {
|
|
||||||
result = append(result, int(num))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func toTagsMap(i interface{}) map[string][]string {
|
|
||||||
if i == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
tags, ok := i.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
result := make(map[string][]string)
|
|
||||||
for k, v := range tags {
|
|
||||||
result[k] = toStringArray(v)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func toInt64(i interface{}) *int64 {
|
|
||||||
if i == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
num, ok := i.(float64)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
val := int64(num)
|
|
||||||
return &val
|
|
||||||
}
|
|
||||||
|
|
||||||
func toInt(i interface{}) *int {
|
|
||||||
if i == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
num, ok := i.(float64)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
val := int(num)
|
|
||||||
return &val
|
|
||||||
}
|
|
||||||
|
|
||||||
func toTime(data interface{}) *time.Time {
|
|
||||||
if data == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Ensure data is a float64 which MongoDB uses for numbers
|
|
||||||
timestamp, ok := data.(float64)
|
|
||||||
if !ok {
|
|
||||||
fmt.Println("Invalid timestamp format")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
t := time.Unix(int64(timestamp), 0).UTC()
|
|
||||||
return &t
|
|
||||||
}
|
|
||||||
|
95
utils/decode.go
Normal file
95
utils/decode.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToStringArray(i interface{}) []string {
|
||||||
|
if i == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
arr, ok := i.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var result []string
|
||||||
|
for _, v := range arr {
|
||||||
|
str, ok := v.(string)
|
||||||
|
if ok {
|
||||||
|
result = append(result, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToIntArray(i interface{}) []int {
|
||||||
|
if i == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
arr, ok := i.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var result []int
|
||||||
|
for _, v := range arr {
|
||||||
|
num, ok := v.(float64)
|
||||||
|
if ok {
|
||||||
|
result = append(result, int(num))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToTagsMap(i interface{}) map[string][]string {
|
||||||
|
if i == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tags, ok := i.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result := make(map[string][]string)
|
||||||
|
for k, v := range tags {
|
||||||
|
result[k] = ToStringArray(v)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToInt64(i interface{}) *int64 {
|
||||||
|
if i == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
num, ok := i.(float64)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
val := int64(num)
|
||||||
|
return &val
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToInt(i interface{}) *int {
|
||||||
|
if i == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
num, ok := i.(float64)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
val := int(num)
|
||||||
|
return &val
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToTime(data interface{}) *time.Time {
|
||||||
|
if data == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Ensure data is a float64 which MongoDB uses for numbers
|
||||||
|
timestamp, ok := data.(float64)
|
||||||
|
if !ok {
|
||||||
|
fmt.Println("Invalid timestamp format")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t := time.Unix(int64(timestamp), 0).UTC()
|
||||||
|
return &t
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user