forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

knotserver,knotclient: switch cursor format to unix timestamp

Signed-off-by: oppiliappan <me@oppi.li>

oppi.li 094b3606 e9dac7d1

verified
Changed files
+40 -24
knotclient
knotserver
nix
+20 -14
knotclient/events.go
··· 7 7 "log/slog" 8 8 "math/rand" 9 9 "net/url" 10 + "strconv" 10 11 "sync" 11 12 "time" 12 13 ··· 62 63 } 63 64 64 65 type CursorStore interface { 65 - Set(knot, cursor string) 66 - Get(knot string) (cursor string) 66 + Set(knot string, cursor int64) 67 + Get(knot string) (cursor int64) 67 68 } 68 69 69 70 type RedisCursorStore struct { ··· 80 81 cursorKey = "cursor:%s" 81 82 ) 82 83 83 - func (r *RedisCursorStore) Set(knot, cursor string) { 84 + func (r *RedisCursorStore) Set(knot string, cursor int64) { 84 85 key := fmt.Sprintf(cursorKey, knot) 85 86 r.rdb.Set(context.Background(), key, cursor, 0) 86 87 } 87 88 88 - func (r *RedisCursorStore) Get(knot string) (cursor string) { 89 + func (r *RedisCursorStore) Get(knot string) (cursor int64) { 89 90 key := fmt.Sprintf(cursorKey, knot) 90 91 val, err := r.rdb.Get(context.Background(), key).Result() 91 92 if err != nil { 92 - return "" 93 + return 0 93 94 } 94 95 95 - return val 96 + cursor, err = strconv.ParseInt(val, 10, 64) 97 + if err != nil { 98 + return 0 // optionally log parsing error 99 + } 100 + 101 + return cursor 96 102 } 97 103 98 104 type MemoryCursorStore struct { 99 105 store sync.Map 100 106 } 101 107 102 - func (m *MemoryCursorStore) Set(knot, cursor string) { 108 + func (m *MemoryCursorStore) Set(knot string, cursor int64) { 103 109 m.store.Store(knot, cursor) 104 110 } 105 111 106 - func (m *MemoryCursorStore) Get(knot string) (cursor string) { 112 + func (m *MemoryCursorStore) Get(knot string) (cursor int64) { 107 113 if result, ok := m.store.Load(knot); ok { 108 - if val, ok := result.(string); ok { 114 + if val, ok := result.(int64); ok { 109 115 return val 110 116 } 111 117 } 112 118 113 - return "" 119 + return 0 114 120 } 115 121 116 - func (e *EventConsumer) buildUrl(s EventSource, cursor string) (*url.URL, error) { 122 + func (e *EventConsumer) buildUrl(s EventSource, cursor int64) (*url.URL, error) { 117 123 scheme := "wss" 118 124 if e.cfg.Dev { 119 125 scheme = "ws" ··· 124 130 return nil, err 125 131 } 126 132 127 - if cursor != "" { 133 + if cursor != 0 { 128 134 query := url.Values{} 129 - query.Add("cursor", cursor) 135 + query.Add("cursor", fmt.Sprintf("%d", cursor)) 130 136 u.RawQuery = query.Encode() 131 137 } 132 138 return u, nil ··· 222 228 } 223 229 224 230 // update cursor 225 - c.cfg.CursorStore.Set(j.source.Knot, msg.Rkey) 231 + c.cfg.CursorStore.Set(j.source.Knot, time.Now().Unix()) 226 232 227 233 if err := c.cfg.ProcessFunc(j.source, msg); err != nil { 228 234 c.logger.Error("error processing message", "source", j.source, "err", err)
+10 -6
knotserver/db/events.go
··· 10 10 Rkey string `json:"rkey"` 11 11 Nsid string `json:"nsid"` 12 12 EventJson string `json:"event"` 13 + Created int64 `json:"created"` 13 14 } 14 15 15 16 func (d *DB) InsertEvent(event Event, notifier *notifier.Notifier) error { 17 + 16 18 _, err := d.db.Exec( 17 19 `insert into events (rkey, nsid, event) values (?, ?, ?)`, 18 20 event.Rkey, ··· 25 27 return err 26 28 } 27 29 28 - func (d *DB) GetEvents(cursor string) ([]Event, error) { 30 + func (d *DB) GetEvents(cursor int64) ([]Event, error) { 29 31 whereClause := "" 30 32 args := []any{} 31 - if cursor != "" { 32 - whereClause = "where rkey > ?" 33 + if cursor > 0 { 34 + whereClause = "where created > ?" 33 35 args = append(args, cursor) 34 36 } 35 37 36 38 query := fmt.Sprintf(` 37 - select rkey, nsid, event 39 + select rkey, nsid, event, created 38 40 from events 39 41 %s 40 - order by rkey asc 42 + order by created asc 41 43 limit 100 42 44 `, whereClause) 43 45 ··· 50 52 var evts []Event 51 53 for rows.Next() { 52 54 var ev Event 53 - rows.Scan(&ev.Rkey, &ev.Nsid, &ev.EventJson) 55 + if err := rows.Scan(&ev.Rkey, &ev.Nsid, &ev.EventJson, &ev.Created); err != nil { 56 + return nil, err 57 + } 54 58 evts = append(evts, ev) 55 59 } 56 60
+1
knotserver/db/init.go
··· 48 48 rkey text not null, 49 49 nsid text not null, 50 50 event text not null, -- json 51 + created integer not null default (strftime('%s', 'now')), 51 52 primary key (rkey, nsid) 52 53 ); 53 54 `)
+8 -3
knotserver/events.go
··· 4 4 "context" 5 5 "encoding/json" 6 6 "net/http" 7 + "strconv" 7 8 "time" 8 9 9 10 "github.com/gorilla/websocket" ··· 42 43 } 43 44 }() 44 45 45 - cursor := r.URL.Query().Get("cursor") 46 + cursorStr := r.URL.Query().Get("cursor") 47 + cursor, err := strconv.ParseInt(cursorStr, 10, 64) 48 + if err != nil { 49 + l.Error("empty or invalid cursor, defaulting to zero", "invalidCursor", cursorStr) 50 + } 46 51 47 52 // complete backfill first before going to live data 48 53 l.Debug("going through backfill", "cursor", cursor) ··· 74 79 } 75 80 } 76 81 77 - func (h *Handle) streamOps(conn *websocket.Conn, cursor *string) error { 82 + func (h *Handle) streamOps(conn *websocket.Conn, cursor *int64) error { 78 83 events, err := h.db.GetEvents(*cursor) 79 84 if err != nil { 80 85 h.l.Error("failed to fetch events from db", "err", err, "cursor", cursor) ··· 105 110 h.l.Debug("err", "err", err) 106 111 return err 107 112 } 108 - *cursor = event.Rkey 113 + *cursor = event.Created 109 114 } 110 115 111 116 return nil
+1 -1
nix/vm.nix
··· 21 21 g = config.services.tangled-knot.gitUser; 22 22 in [ 23 23 "d /var/lib/knot 0770 ${u} ${g} - -" # Create the directory first 24 - "f+ /var/lib/knot/secret 0660 ${u} ${g} - KNOT_SERVER_SECRET=40b4db20544e37a12ba3ed7353d4d4421a30e0593385068d2ef85263495794d8" 24 + "f+ /var/lib/knot/secret 0660 ${u} ${g} - KNOT_SERVER_SECRET=16154910ef55fe48121082c0b51fc0e360a8b15eb7bda7991d88dc9f7684427a" 25 25 ]; 26 26 services.tangled-knot = { 27 27 enable = true;