mirror of
https://github.com/aljazceru/khatru.git
synced 2025-12-26 09:44:22 +01:00
turn relayer into a server framework and put actual relay code into ./basic
This commit is contained in:
7
basic/Makefile
Normal file
7
basic/Makefile
Normal file
@@ -0,0 +1,7 @@
|
||||
relayer: $(shell find . -name "*.go")
|
||||
go build -ldflags="-s -w" -o ./relayer
|
||||
|
||||
deploy: relayer
|
||||
ssh root@turgot 'systemctl stop relayer'
|
||||
scp relayer turgot:relayer/relayer
|
||||
ssh root@turgot 'systemctl start relayer'
|
||||
15
basic/cleanup.go
Normal file
15
basic/cleanup.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// every hour, delete all very old events
|
||||
func cleanupRoutine(db *sqlx.DB) {
|
||||
for {
|
||||
time.Sleep(60 * time.Minute)
|
||||
db.Exec(`DELETE FROM event WHERE created_at < $1`, time.Now().AddDate(0, -3, 0))
|
||||
}
|
||||
}
|
||||
44
basic/main.go
Normal file
44
basic/main.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fiatjaf/relayer"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/jmoiron/sqlx/reflectx"
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
)
|
||||
|
||||
type BasicRelay struct {
|
||||
PostgresDatabase string `envconfig:"POSTGRESQL_DATABASE"`
|
||||
|
||||
DB *sqlx.DB
|
||||
}
|
||||
|
||||
func (b *BasicRelay) Name() string {
|
||||
return "BasicRelay"
|
||||
}
|
||||
|
||||
func (b *BasicRelay) Init() error {
|
||||
err := envconfig.Process("", b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't process envconfig: %w", err)
|
||||
}
|
||||
|
||||
if db, err := initDB(b.PostgresDatabase); err != nil {
|
||||
return fmt.Errorf("failed to open database: %w", err)
|
||||
} else {
|
||||
db.Mapper = reflectx.NewMapperFunc("json", sqlx.NameMapper)
|
||||
b.DB = db
|
||||
}
|
||||
|
||||
go cleanupRoutine(b.DB)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
var b BasicRelay
|
||||
|
||||
relayer.Start(&b)
|
||||
}
|
||||
33
basic/postgresql.go
Normal file
33
basic/postgresql.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func initDB(dburl string) (*sqlx.DB, error) {
|
||||
db, err := sqlx.Connect("postgres", dburl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS event (
|
||||
id text NOT NULL,
|
||||
pubkey text NOT NULL,
|
||||
created_at integer NOT NULL,
|
||||
kind integer NOT NULL,
|
||||
tags jsonb NOT NULL,
|
||||
content text NOT NULL,
|
||||
sig text NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS ididx ON event (id);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS pubkeytimeidx ON event (pubkey, created_at);
|
||||
`)
|
||||
log.Print(err)
|
||||
return db, nil
|
||||
}
|
||||
|
||||
const tagConditions = `jsonb_path_match(tags, '$[*][1] == $value', jsonb_build_object('value', ?::text))`
|
||||
86
basic/query.go
Normal file
86
basic/query.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fiatjaf/go-nostr/event"
|
||||
"github.com/fiatjaf/go-nostr/filter"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (b *BasicRelay) QueryEvents(
|
||||
filter *filter.EventFilter,
|
||||
) (events []event.Event, err error) {
|
||||
var conditions []string
|
||||
var params []interface{}
|
||||
|
||||
if filter == nil {
|
||||
err = errors.New("filter cannot be null")
|
||||
return
|
||||
}
|
||||
|
||||
if filter.ID != "" {
|
||||
conditions = append(conditions, "id = ?")
|
||||
params = append(params, filter.ID)
|
||||
}
|
||||
|
||||
if filter.Kind != nil && *filter.Kind != 0 {
|
||||
conditions = append(conditions, "kind = ?")
|
||||
params = append(params, filter.Kind)
|
||||
}
|
||||
|
||||
if filter.Authors != nil {
|
||||
if len(filter.Authors) == 0 {
|
||||
// authors being [] means you won't get anything
|
||||
return
|
||||
} else {
|
||||
inkeys := make([]string, 0, len(filter.Authors))
|
||||
for _, key := range filter.Authors {
|
||||
// to prevent sql attack here we will check if
|
||||
// these keys are valid 32byte hex
|
||||
parsed, err := hex.DecodeString(key)
|
||||
if err != nil || len(parsed) != 32 {
|
||||
continue
|
||||
}
|
||||
inkeys = append(inkeys, fmt.Sprintf("'%x'", parsed))
|
||||
}
|
||||
conditions = append(conditions, `pubkey IN (`+strings.Join(inkeys, ",")+`)`)
|
||||
}
|
||||
}
|
||||
|
||||
if filter.TagEvent != "" {
|
||||
conditions = append(conditions, tagConditions)
|
||||
params = append(params, filter.TagEvent)
|
||||
}
|
||||
|
||||
if filter.TagProfile != "" {
|
||||
conditions = append(conditions, tagConditions)
|
||||
params = append(params, filter.TagProfile)
|
||||
}
|
||||
|
||||
if filter.Since != 0 {
|
||||
conditions = append(conditions, "created_at > ?")
|
||||
params = append(params, filter.Since)
|
||||
}
|
||||
|
||||
if len(conditions) == 0 {
|
||||
// fallback
|
||||
conditions = append(conditions, "true")
|
||||
}
|
||||
|
||||
query := b.DB.Rebind("SELECT * FROM event WHERE " +
|
||||
strings.Join(conditions, " AND ") +
|
||||
" ORDER BY created_at LIMIT 100")
|
||||
|
||||
err = b.DB.Select(&events, query, params...)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
log.Warn().Err(err).Interface("filter", filter).Msg("failed to fetch events")
|
||||
err = fmt.Errorf("failed to fetch events: %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
55
basic/save.go
Normal file
55
basic/save.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fiatjaf/go-nostr/event"
|
||||
)
|
||||
|
||||
func (b *BasicRelay) SaveEvent(evt *event.Event) error {
|
||||
// disallow large contents
|
||||
if len(evt.Content) > 1000 {
|
||||
return errors.New("event content too large")
|
||||
}
|
||||
|
||||
// react to different kinds of events
|
||||
switch evt.Kind {
|
||||
case event.KindSetMetadata:
|
||||
// delete past set_metadata events from this user
|
||||
b.DB.Exec(`DELETE FROM event WHERE pubkey = $1 AND kind = 0`, evt.PubKey)
|
||||
case event.KindRecommendServer:
|
||||
// delete past recommend_server events equal to this one
|
||||
b.DB.Exec(`DELETE FROM event WHERE pubkey = $1 AND kind = 2 AND content = $2`,
|
||||
evt.PubKey, evt.Content)
|
||||
case event.KindContactList:
|
||||
// delete past contact lists from this same pubkey
|
||||
b.DB.Exec(`DELETE FROM event WHERE pubkey = $1 AND kind = 3`, evt.PubKey)
|
||||
default:
|
||||
// delete all but the 10 most recent ones
|
||||
b.DB.Exec(`DELETE FROM event WHERE pubkey = $1 AND kind = $2 AND created_at < (
|
||||
SELECT created_at FROM event WHERE pubkey = $1
|
||||
ORDER BY created_at DESC OFFSET 10 LIMIT 1
|
||||
)`,
|
||||
evt.PubKey, evt.Kind)
|
||||
}
|
||||
|
||||
// insert
|
||||
tagsj, _ := json.Marshal(evt.Tags)
|
||||
_, err := b.DB.Exec(`
|
||||
INSERT INTO event (id, pubkey, created_at, kind, tags, content, sig)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
`, evt.ID, evt.PubKey, evt.CreatedAt, evt.Kind, tagsj, evt.Content, evt.Sig)
|
||||
if err != nil {
|
||||
if strings.Index(err.Error(), "UNIQUE") != -1 {
|
||||
// already exists
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed to save event from %s", evt.PubKey)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user