implemented reactions module
This commit is contained in:
+19
-11
@@ -146,12 +146,26 @@ type Reactions interface {
|
||||
SetAllowed(author string, date time.Time, emojis []string) error
|
||||
Allowed(author string, date time.Time) ([]string, error)
|
||||
|
||||
AddExternal(user, domain, author string, date time.Time, emoji string, sig []byte) error
|
||||
RemoveExternal(user, domain, author string, date time.Time, emoji string) error
|
||||
ToExternal(domain, author string, date time.Time) ([]Reaction, error)
|
||||
Add(user, author, domain string, date time.Time, emoji string, sig []byte) error
|
||||
Remove(user, author, domain string, date time.Time, emoji string) error
|
||||
OfUser(user string, limit, offset int) ([]ReactionToForeign, error)
|
||||
ToContent(author, domain string, date time.Time, limit, offset int) ([]ReactionOfDomestic, error)
|
||||
|
||||
UpdateReference(author string, date time.Time, domain, emoji string, delta int) error
|
||||
ListReactions(author string, date time.Time) ([]ReactionCounter, error)
|
||||
UpdateReference(owner string, date time.Time, domain, emoji string, delta int) error
|
||||
ListReactions(owner string, date time.Time) ([]ReactionCounter, error)
|
||||
}
|
||||
|
||||
type ReactionToForeign struct {
|
||||
Domain string `json:"domain"`
|
||||
Author string `json:"author"`
|
||||
Date time.Time `json:"date"`
|
||||
Emoji string `json:"emoji"`
|
||||
}
|
||||
|
||||
type ReactionOfDomestic struct {
|
||||
User string `json:"user"`
|
||||
Emoji string `json:"emoji"`
|
||||
Signature []byte `json:"signature"`
|
||||
}
|
||||
|
||||
type ReactionCounter struct {
|
||||
@@ -159,12 +173,6 @@ type ReactionCounter struct {
|
||||
Count uint64 `json:"count"`
|
||||
}
|
||||
|
||||
type Reaction struct {
|
||||
User string `json:"user"`
|
||||
Emoji string `json:"emoji"`
|
||||
Signature []byte `json:"signature"`
|
||||
}
|
||||
|
||||
// type Comments interface {
|
||||
// Enable(typ string, id uuid.UUID) error
|
||||
// Disable(typ string, id uuid.UUID) error
|
||||
|
||||
@@ -44,7 +44,7 @@ type DB struct {
|
||||
email *email
|
||||
chat *chat
|
||||
posts *posts
|
||||
// reactions *reactions
|
||||
reactions *reactions
|
||||
// comments *comments
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func New(path string) (*DB, error) {
|
||||
email: &email{db: db},
|
||||
chat: &chat{db: db},
|
||||
posts: &posts{db: db},
|
||||
// reactions: &reactions{db: db, flushInterval: time.Second},
|
||||
reactions: &reactions{db: db, flushInterval: time.Second},
|
||||
// comments: &comments{db: ldb},
|
||||
}, nil
|
||||
}
|
||||
@@ -106,8 +106,7 @@ func (d *DB) Files() database.Files { return d.files }
|
||||
func (d *DB) Email() database.Email { return d.email }
|
||||
func (d *DB) Chat() database.Chat { return d.chat }
|
||||
func (d *DB) Posts() database.Posts { return d.posts }
|
||||
|
||||
// func (d *DB) Reactions() database.Reactions { return d.reactions }
|
||||
func (d *DB) Reactions() database.Reactions { return d.reactions }
|
||||
|
||||
// func (d *DB) Comments() database.Comments { return d.comments }
|
||||
|
||||
|
||||
+217
-160
@@ -1,206 +1,263 @@
|
||||
package leveldb
|
||||
|
||||
// import (
|
||||
// "bytes"
|
||||
// "encoding/binary"
|
||||
// "encoding/gob"
|
||||
// "fmt"
|
||||
// "sync"
|
||||
// "time"
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
// "github.com/d1nch8g/mesh/database"
|
||||
// "github.com/syndtr/goleveldb/leveldb"
|
||||
// "github.com/syndtr/goleveldb/leveldb/util"
|
||||
// )
|
||||
"github.com/d1nch8g/mesh/database"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
// type reactions struct {
|
||||
// db *leveldb.DB
|
||||
// counters sync.Map
|
||||
// flushInterval time.Duration
|
||||
// }
|
||||
type reactions struct {
|
||||
db *leveldb.DB
|
||||
counters sync.Map
|
||||
flushInterval time.Duration
|
||||
}
|
||||
|
||||
// func (r *reactions) SetAllowed(author string, date time.Time, emojis []string) error {
|
||||
// k := key(author, prefixReactionsAllowed, formatTime(date))
|
||||
func (r *reactions) SetAllowed(owner string, date time.Time, emojis []string) error {
|
||||
k := key(owner, prefixReactionsAllowed, formatTime(date))
|
||||
|
||||
// var buf bytes.Buffer
|
||||
// if err := gob.NewEncoder(&buf).Encode(emojis); err != nil {
|
||||
// return fmt.Errorf("encode allowed reactions for %q: %w", author, err)
|
||||
// }
|
||||
var buf bytes.Buffer
|
||||
if err := gob.NewEncoder(&buf).Encode(emojis); err != nil {
|
||||
return fmt.Errorf("encode allowed reactions for %q: %w", owner, err)
|
||||
}
|
||||
|
||||
// if err := r.db.Put(k, buf.Bytes(), woSync); err != nil {
|
||||
// return fmt.Errorf("store allowed reactions for %q: %w", author, err)
|
||||
// }
|
||||
if err := r.db.Put(k, buf.Bytes(), woSync); err != nil {
|
||||
return fmt.Errorf("store allowed reactions for %q: %w", owner, err)
|
||||
}
|
||||
|
||||
// return nil
|
||||
// }
|
||||
return nil
|
||||
}
|
||||
|
||||
// func (r *reactions) Allowed(author string, date time.Time) ([]string, error) {
|
||||
// k := key(author, prefixReactionsAllowed, formatTime(date))
|
||||
func (r *reactions) Allowed(owner string, date time.Time) ([]string, error) {
|
||||
k := key(owner, prefixReactionsAllowed, formatTime(date))
|
||||
|
||||
// data, err := r.db.Get(k, nil)
|
||||
// if err != nil {
|
||||
// if err == leveldb.ErrNotFound {
|
||||
// return nil, database.ErrNotFound
|
||||
// }
|
||||
// return nil, fmt.Errorf("read allowed reactions for %q: %w", author, err)
|
||||
// }
|
||||
data, err := r.db.Get(k, nil)
|
||||
if err != nil {
|
||||
if err == leveldb.ErrNotFound {
|
||||
return nil, database.ErrNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("read allowed reactions for %q: %w", owner, err)
|
||||
}
|
||||
|
||||
// var emojis []string
|
||||
// if err = gob.NewDecoder(bytes.NewReader(data)).Decode(&emojis); err != nil {
|
||||
// return nil, fmt.Errorf("decode allowed reactions for %q: %w", author, err)
|
||||
// }
|
||||
var emojis []string
|
||||
if err = gob.NewDecoder(bytes.NewReader(data)).Decode(&emojis); err != nil {
|
||||
return nil, fmt.Errorf("decode allowed reactions for %q: %w", owner, err)
|
||||
}
|
||||
|
||||
// return emojis, nil
|
||||
// }
|
||||
return emojis, nil
|
||||
}
|
||||
|
||||
// func (r *reactions) AddExternal(user, domain, author string, date time.Time, emoji string, sig []byte) error {
|
||||
// domesticKey := key(user, prefixReactionsOut, domain, formatTime(date), emoji)
|
||||
// foreignKey := key(domain, prefixReactionsIn, user, formatTime(date), emoji)
|
||||
func (r *reactions) Add(user, author, domain string, date time.Time, emoji string, sig []byte) error {
|
||||
ts := formatTime(date)
|
||||
|
||||
// batch := new(leveldb.Batch)
|
||||
// defer batch.Reset()
|
||||
domesticKey := key(user, prefixReactionsOut, author, domain, ts, emoji)
|
||||
foreignKey := key(domain, prefixReactionsIn, author, ts, user, emoji)
|
||||
|
||||
// batch.Put(domesticKey, sig)
|
||||
// batch.Put(foreignKey, sig)
|
||||
batch := new(leveldb.Batch)
|
||||
defer batch.Reset()
|
||||
|
||||
// if err := r.db.Write(batch, woSync); err != nil {
|
||||
// return fmt.Errorf("store reaction for %q: %w", user, err)
|
||||
// }
|
||||
batch.Put(domesticKey, sig)
|
||||
batch.Put(foreignKey, sig)
|
||||
|
||||
// return nil
|
||||
// }
|
||||
if err := r.db.Write(batch, woSync); err != nil {
|
||||
return fmt.Errorf("store reaction for %q: %w", user, err)
|
||||
}
|
||||
|
||||
// func (r *reactions) RemoveExternal(user, domain, author string, date time.Time, emoji string) error {
|
||||
// domesticKey := key(user, prefixReactionsOut, domain, formatTime(date), emoji)
|
||||
// foreignKey := key(domain, prefixReactionsIn, user, formatTime(date), emoji)
|
||||
return nil
|
||||
}
|
||||
|
||||
// _, err := r.db.Get(domesticKey, nil)
|
||||
// if err != nil {
|
||||
// if err == leveldb.ErrNotFound {
|
||||
// return database.ErrNotFound
|
||||
// }
|
||||
// return fmt.Errorf("check reaction for %q: %w", user, err)
|
||||
// }
|
||||
func (r *reactions) Remove(user, author, domain string, date time.Time, emoji string) error {
|
||||
ts := formatTime(date)
|
||||
|
||||
// batch := new(leveldb.Batch)
|
||||
// defer batch.Reset()
|
||||
domesticKey := key(user, prefixReactionsOut, author, domain, ts, emoji)
|
||||
foreignKey := key(domain, prefixReactionsIn, author, ts, user, emoji)
|
||||
|
||||
// batch.Delete(domesticKey)
|
||||
// batch.Delete(foreignKey)
|
||||
_, err := r.db.Get(domesticKey, nil)
|
||||
if err != nil {
|
||||
if err == leveldb.ErrNotFound {
|
||||
return database.ErrNotFound
|
||||
}
|
||||
return fmt.Errorf("check reaction for %q: %w", user, err)
|
||||
}
|
||||
|
||||
// if err := r.db.Write(batch, woSync); err != nil {
|
||||
// return fmt.Errorf("delete reaction for %q: %w", user, err)
|
||||
// }
|
||||
batch := new(leveldb.Batch)
|
||||
defer batch.Reset()
|
||||
|
||||
// return nil
|
||||
// }
|
||||
batch.Delete(domesticKey)
|
||||
batch.Delete(foreignKey)
|
||||
|
||||
// func (r *reactions) ToExternal(domain, author string, date time.Time) ([]database.Reaction, error) {
|
||||
// prefix := key(domain, prefixReactionsIn, author, formatTime(date))
|
||||
if err := r.db.Write(batch, woSync); err != nil {
|
||||
return fmt.Errorf("delete reaction for %q: %w", user, err)
|
||||
}
|
||||
|
||||
// iter := r.db.NewIterator(util.BytesPrefix(prefix), nil)
|
||||
// defer iter.Release()
|
||||
return nil
|
||||
}
|
||||
|
||||
// var reactions []database.Reaction
|
||||
// for iter.Next() {
|
||||
// parts := bytes.Split(iter.Key(), []byte(":"))
|
||||
// emoji := string(parts[len(parts)-1])
|
||||
func (r *reactions) OfUser(user string, limit, offset int) ([]database.ReactionToForeign, error) {
|
||||
prefix := key(user, prefixReactionsOut)
|
||||
|
||||
// reactions = append(reactions, database.Reaction{
|
||||
// User: author,
|
||||
// Emoji: emoji,
|
||||
// Signature: iter.Value(),
|
||||
// })
|
||||
// }
|
||||
iter := r.db.NewIterator(util.BytesPrefix(prefix), nil)
|
||||
defer iter.Release()
|
||||
|
||||
// return reactions, nil
|
||||
// }
|
||||
var reactions []database.ReactionToForeign
|
||||
skipped := 0
|
||||
for iter.Next() {
|
||||
if skipped < offset {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
// func (r *reactions) UpdateReference(author string, date time.Time, domain, emoji string, delta int) error {
|
||||
// domainKey := key(author, prefixReactionsRef, formatTime(date), "perdomain", domain, emoji)
|
||||
// totalKey := key(author, prefixReactionsRef, formatTime(date), "total", emoji)
|
||||
parts := bytes.Split(iter.Key(), []byte(":"))
|
||||
author := string(parts[len(parts)-4])
|
||||
domain := string(parts[len(parts)-3])
|
||||
dateBytes := parts[len(parts)-2]
|
||||
emoji := string(parts[len(parts)-1])
|
||||
|
||||
// r.addHot(domainKey, delta)
|
||||
// r.addHot(totalKey, delta)
|
||||
ts := binary.BigEndian.Uint64(dateBytes)
|
||||
date := time.Unix(0, int64(ts))
|
||||
|
||||
// return nil
|
||||
// }
|
||||
reactions = append(reactions, database.ReactionToForeign{
|
||||
Domain: domain,
|
||||
Author: author,
|
||||
Date: date,
|
||||
Emoji: emoji,
|
||||
})
|
||||
|
||||
// func (r *reactions) addHot(k []byte, delta int) {
|
||||
// cnt := &counter{}
|
||||
if len(reactions) >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// entry, ok := r.counters.LoadOrStore(string(k), cnt)
|
||||
// if !ok {
|
||||
// cnt.mu.Lock()
|
||||
// data, err := r.db.Get(k, nil)
|
||||
// if err == nil && len(data) == 8 {
|
||||
// cnt.val = binary.LittleEndian.Uint64(data)
|
||||
// }
|
||||
// cnt.mu.Unlock()
|
||||
// } else {
|
||||
// cnt = entry.(*counter)
|
||||
// }
|
||||
return reactions, nil
|
||||
}
|
||||
|
||||
// cnt.mu.Lock()
|
||||
// val := int64(cnt.val) + int64(delta)
|
||||
// if val < 0 {
|
||||
// val = 0
|
||||
// }
|
||||
// cnt.val = uint64(val)
|
||||
func (r *reactions) ToContent(author, domain string, date time.Time, limit, offset int) ([]database.ReactionOfDomestic, error) {
|
||||
prefix := key(domain, prefixReactionsIn, author, formatTime(date))
|
||||
|
||||
// if !cnt.jobRunning {
|
||||
// cnt.jobRunning = true
|
||||
// go r.flushCounter(k, cnt)
|
||||
// }
|
||||
// cnt.mu.Unlock()
|
||||
// }
|
||||
iter := r.db.NewIterator(util.BytesPrefix(prefix), nil)
|
||||
defer iter.Release()
|
||||
|
||||
// func (r *reactions) flushCounter(k []byte, ctrl *counter) {
|
||||
// time.Sleep(r.flushInterval)
|
||||
var reactions []database.ReactionOfDomestic
|
||||
skipped := 0
|
||||
for iter.Next() {
|
||||
if skipped < offset {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
// ctrl.mu.Lock()
|
||||
// defer ctrl.mu.Unlock()
|
||||
parts := bytes.Split(iter.Key(), []byte(":"))
|
||||
user := string(parts[len(parts)-2])
|
||||
emoji := string(parts[len(parts)-1])
|
||||
|
||||
// if ctrl.val == 0 {
|
||||
// r.db.Delete(k, woSync)
|
||||
// } else {
|
||||
// var buf [8]byte
|
||||
// binary.LittleEndian.PutUint64(buf[:], ctrl.val)
|
||||
// r.db.Put(k, buf[:], woSync)
|
||||
// ctrl.val = 0
|
||||
// }
|
||||
sig := make([]byte, len(iter.Value()))
|
||||
copy(sig, iter.Value())
|
||||
|
||||
// ctrl.jobRunning = false
|
||||
// }
|
||||
reactions = append(reactions, database.ReactionOfDomestic{
|
||||
User: user,
|
||||
Emoji: emoji,
|
||||
Signature: sig,
|
||||
})
|
||||
|
||||
// func (r *reactions) ListReactions(author string, date time.Time) ([]database.ReactionCounter, error) {
|
||||
// prefix := key(author, prefixReactionsRef, formatTime(date), "total")
|
||||
if len(reactions) >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// iter := r.db.NewIterator(util.BytesPrefix(prefix), nil)
|
||||
// defer iter.Release()
|
||||
return reactions, nil
|
||||
}
|
||||
|
||||
// var result []database.ReactionCounter
|
||||
// for iter.Next() {
|
||||
// parts := bytes.Split(iter.Key(), []byte(":"))
|
||||
// emoji := string(parts[len(parts)-1])
|
||||
func (r *reactions) UpdateReference(owner string, date time.Time, domain, emoji string, delta int) error {
|
||||
ts := formatTime(date)
|
||||
domainKey := key(owner, prefixReactionsRef, ts, "perdomain", domain, emoji)
|
||||
totalKey := key(owner, prefixReactionsRef, ts, "total", emoji)
|
||||
|
||||
// diskVal := binary.LittleEndian.Uint64(iter.Value())
|
||||
// count := diskVal
|
||||
r.addHot(domainKey, delta)
|
||||
r.addHot(totalKey, delta)
|
||||
|
||||
// if entry, ok := r.counters.Load(string(iter.Key())); ok {
|
||||
// cnt := entry.(*counter)
|
||||
// cnt.mu.RLock()
|
||||
// count += cnt.val
|
||||
// cnt.mu.RUnlock()
|
||||
// }
|
||||
return nil
|
||||
}
|
||||
|
||||
// if count > 0 {
|
||||
// result = append(result, database.ReactionCounter{
|
||||
// Emoji: emoji,
|
||||
// Count: count,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
func (r *reactions) addHot(k []byte, delta int) {
|
||||
cnt := &counter{}
|
||||
|
||||
// return result, nil
|
||||
// }
|
||||
entry, ok := r.counters.LoadOrStore(string(k), cnt)
|
||||
if !ok {
|
||||
cnt.mu.Lock()
|
||||
data, err := r.db.Get(k, nil)
|
||||
if err == nil && len(data) == 8 {
|
||||
cnt.val = binary.LittleEndian.Uint64(data)
|
||||
}
|
||||
cnt.mu.Unlock()
|
||||
} else {
|
||||
cnt = entry.(*counter)
|
||||
}
|
||||
|
||||
cnt.mu.Lock()
|
||||
val := int64(cnt.val) + int64(delta)
|
||||
if val < 0 {
|
||||
val = 0
|
||||
}
|
||||
cnt.val = uint64(val)
|
||||
|
||||
if !cnt.jobRunning {
|
||||
cnt.jobRunning = true
|
||||
go r.flushCounter(k, cnt)
|
||||
}
|
||||
cnt.mu.Unlock()
|
||||
}
|
||||
|
||||
func (r *reactions) flushCounter(k []byte, ctrl *counter) {
|
||||
time.Sleep(r.flushInterval)
|
||||
|
||||
ctrl.mu.Lock()
|
||||
defer ctrl.mu.Unlock()
|
||||
|
||||
if ctrl.val == 0 {
|
||||
r.db.Delete(k, woSync)
|
||||
} else {
|
||||
var buf [8]byte
|
||||
binary.LittleEndian.PutUint64(buf[:], ctrl.val)
|
||||
r.db.Put(k, buf[:], woSync)
|
||||
ctrl.val = 0
|
||||
}
|
||||
|
||||
ctrl.jobRunning = false
|
||||
}
|
||||
|
||||
func (r *reactions) ListReactions(owner string, date time.Time) ([]database.ReactionCounter, error) {
|
||||
prefix := key(owner, prefixReactionsRef, formatTime(date), "total")
|
||||
|
||||
iter := r.db.NewIterator(util.BytesPrefix(prefix), nil)
|
||||
defer iter.Release()
|
||||
|
||||
var result []database.ReactionCounter
|
||||
for iter.Next() {
|
||||
parts := bytes.Split(iter.Key(), []byte(":"))
|
||||
emoji := string(parts[len(parts)-1])
|
||||
|
||||
diskVal := binary.LittleEndian.Uint64(iter.Value())
|
||||
count := diskVal
|
||||
|
||||
if entry, ok := r.counters.Load(string(iter.Key())); ok {
|
||||
cnt := entry.(*counter)
|
||||
cnt.mu.RLock()
|
||||
count += cnt.val
|
||||
cnt.mu.RUnlock()
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
result = append(result, database.ReactionCounter{
|
||||
Emoji: emoji,
|
||||
Count: count,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
+314
-175
@@ -1,220 +1,359 @@
|
||||
package leveldb
|
||||
|
||||
// import (
|
||||
// "testing"
|
||||
// "time"
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
// "github.com/d1nch8g/mesh/database"
|
||||
// "github.com/stretchr/testify/require"
|
||||
// )
|
||||
"github.com/d1nch8g/mesh/database"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// func TestReactions_SetAllowed(t *testing.T) {
|
||||
// db, cleanup := openDB(t)
|
||||
// defer cleanup()
|
||||
func TestReactions_SetAllowed(t *testing.T) {
|
||||
db, cleanup := openDB(t)
|
||||
defer cleanup()
|
||||
|
||||
// reactions := db.Reactions()
|
||||
// author := "masha@d1.com"
|
||||
// date := time.Now()
|
||||
reactions := db.Reactions()
|
||||
owner := "masha"
|
||||
date := time.Now()
|
||||
|
||||
// t.Run("set allowed emojis", func(t *testing.T) {
|
||||
// err := reactions.SetAllowed(author, date, []string{"👍", "❤️", "🔥"})
|
||||
// require.NoError(t, err)
|
||||
// })
|
||||
t.Run("set allowed emojis", func(t *testing.T) {
|
||||
err := reactions.SetAllowed(owner, date, []string{"👍", "❤️", "🔥"})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// t.Run("overwrite allowed emojis", func(t *testing.T) {
|
||||
// err := reactions.SetAllowed(author, date, []string{"👎"})
|
||||
// require.NoError(t, err)
|
||||
// })
|
||||
t.Run("overwrite allowed emojis", func(t *testing.T) {
|
||||
err := reactions.SetAllowed(owner, date, []string{"👎"})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// t.Run("set empty allowed", func(t *testing.T) {
|
||||
// err := reactions.SetAllowed(author, date.Add(time.Second), []string{})
|
||||
// require.NoError(t, err)
|
||||
// })
|
||||
// }
|
||||
t.Run("set empty allowed", func(t *testing.T) {
|
||||
err := reactions.SetAllowed(owner, date.Add(time.Second), []string{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
// func TestReactions_Allowed(t *testing.T) {
|
||||
// db, cleanup := openDB(t)
|
||||
// defer cleanup()
|
||||
func TestReactions_Allowed(t *testing.T) {
|
||||
db, cleanup := openDB(t)
|
||||
defer cleanup()
|
||||
|
||||
// reactions := db.Reactions()
|
||||
// author := "masha@d1.com"
|
||||
// date := time.Now()
|
||||
reactions := db.Reactions()
|
||||
owner := "masha"
|
||||
date := time.Now()
|
||||
|
||||
// err := reactions.SetAllowed(author, date, []string{"👍", "🔥"})
|
||||
// require.NoError(t, err)
|
||||
err := reactions.SetAllowed(owner, date, []string{"👍", "🔥"})
|
||||
require.NoError(t, err)
|
||||
|
||||
// t.Run("get allowed emojis", func(t *testing.T) {
|
||||
// got, err := reactions.Allowed(author, date)
|
||||
// require.NoError(t, err)
|
||||
// require.Equal(t, []string{"👍", "🔥"}, got)
|
||||
// })
|
||||
t.Run("get allowed emojis", func(t *testing.T) {
|
||||
got, err := reactions.Allowed(owner, date)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{"👍", "🔥"}, got)
|
||||
})
|
||||
|
||||
// t.Run("get non-existing allowed", func(t *testing.T) {
|
||||
// _, err := reactions.Allowed("unknown@d5.io", date)
|
||||
// require.ErrorIs(t, err, database.ErrNotFound)
|
||||
// })
|
||||
// }
|
||||
t.Run("get non-existing allowed", func(t *testing.T) {
|
||||
_, err := reactions.Allowed("unknown", date)
|
||||
require.ErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
|
||||
// func TestReactions_AddExternal(t *testing.T) {
|
||||
// db, cleanup := openDB(t)
|
||||
// defer cleanup()
|
||||
t.Run("get allowed for different time", func(t *testing.T) {
|
||||
_, err := reactions.Allowed(owner, date.Add(time.Hour))
|
||||
require.ErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
// reactions := db.Reactions()
|
||||
// user := "masha@d1.com"
|
||||
// author := "bob@d2.io"
|
||||
// date := time.Now()
|
||||
func TestReactions_Add(t *testing.T) {
|
||||
db, cleanup := openDB(t)
|
||||
defer cleanup()
|
||||
|
||||
// t.Run("add external reaction", func(t *testing.T) {
|
||||
// err := reactions.AddExternal(user, "d1.com", author, date, "👍", []byte("sig123"))
|
||||
// require.NoError(t, err)
|
||||
// })
|
||||
reactions := db.Reactions()
|
||||
user := "masha"
|
||||
author := "bob"
|
||||
domain := "d2.io"
|
||||
date := time.Now()
|
||||
|
||||
// t.Run("add another emoji", func(t *testing.T) {
|
||||
// err := reactions.AddExternal(user, "d1.com", author, date, "🔥", []byte("sig456"))
|
||||
// require.NoError(t, err)
|
||||
// })
|
||||
t.Run("add reaction", func(t *testing.T) {
|
||||
err := reactions.Add(user, author, domain, date, "👍", []byte("sig123"))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// t.Run("add with empty signature", func(t *testing.T) {
|
||||
// err := reactions.AddExternal(user, "d1.com", author, date.Add(time.Second), "❤️", nil)
|
||||
// require.NoError(t, err)
|
||||
// })
|
||||
// }
|
||||
t.Run("add another emoji", func(t *testing.T) {
|
||||
err := reactions.Add(user, author, domain, date, "🔥", []byte("sig456"))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// func TestReactions_RemoveExternal(t *testing.T) {
|
||||
// db, cleanup := openDB(t)
|
||||
// defer cleanup()
|
||||
t.Run("add different user", func(t *testing.T) {
|
||||
err := reactions.Add("petya", author, domain, date, "👍", []byte("sig789"))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// reactions := db.Reactions()
|
||||
// user := "masha@d1.com"
|
||||
// author := "bob@d2.io"
|
||||
// date := time.Now()
|
||||
t.Run("add different content", func(t *testing.T) {
|
||||
err := reactions.Add(user, "alice", "d3.net", date.Add(time.Second), "❤️", []byte("sig000"))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// err := reactions.AddExternal(user, "d1.com", author, date, "👍", []byte("sig"))
|
||||
// require.NoError(t, err)
|
||||
t.Run("add with empty signature", func(t *testing.T) {
|
||||
err := reactions.Add(user, author, domain, date.Add(2*time.Second), "❤️", nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
// t.Run("remove existing reaction", func(t *testing.T) {
|
||||
// err := reactions.RemoveExternal(user, "d1.com", author, date, "👍")
|
||||
// require.NoError(t, err)
|
||||
// })
|
||||
func TestReactions_Remove(t *testing.T) {
|
||||
db, cleanup := openDB(t)
|
||||
defer cleanup()
|
||||
|
||||
// t.Run("remove non-existing returns ErrNotFound", func(t *testing.T) {
|
||||
// err := reactions.RemoveExternal(user, "d1.com", author, date, "🔥")
|
||||
// require.ErrorIs(t, err, database.ErrNotFound)
|
||||
// })
|
||||
// }
|
||||
reactions := db.Reactions()
|
||||
user := "masha"
|
||||
author := "bob"
|
||||
domain := "d2.io"
|
||||
date := time.Now()
|
||||
|
||||
// func TestReactions_ToExternal(t *testing.T) {
|
||||
// db, cleanup := openDB(t)
|
||||
// defer cleanup()
|
||||
err := reactions.Add(user, author, domain, date, "👍", []byte("sig"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// reactions := db.Reactions()
|
||||
// user := "masha@d1.com"
|
||||
// author := "bob@d2.io"
|
||||
// date := time.Now()
|
||||
t.Run("remove existing reaction", func(t *testing.T) {
|
||||
err := reactions.Remove(user, author, domain, date, "👍")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// require.NoError(t, reactions.AddExternal(user, "d1.com", author, date, "👍", []byte("sig1")))
|
||||
// require.NoError(t, reactions.AddExternal(user, "d1.com", author, date, "🔥", []byte("sig2")))
|
||||
t.Run("remove non-existing returns ErrNotFound", func(t *testing.T) {
|
||||
err := reactions.Remove(user, author, domain, date, "🔥")
|
||||
require.ErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
|
||||
// t.Run("list external reactions", func(t *testing.T) {
|
||||
// list, err := reactions.ToExternal("d1.com", author, date)
|
||||
// require.NoError(t, err)
|
||||
// require.Len(t, list, 2)
|
||||
// })
|
||||
t.Run("remove twice returns ErrNotFound", func(t *testing.T) {
|
||||
err := reactions.Remove(user, author, domain, date, "👍")
|
||||
require.ErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
// t.Run("empty external reactions", func(t *testing.T) {
|
||||
// list, err := reactions.ToExternal("unknown.com", author, date)
|
||||
// require.NoError(t, err)
|
||||
// require.Empty(t, list)
|
||||
// })
|
||||
// }
|
||||
func TestReactions_OfUser(t *testing.T) {
|
||||
db, cleanup := openDB(t)
|
||||
defer cleanup()
|
||||
|
||||
// func TestReactions_UpdateReference(t *testing.T) {
|
||||
// db, cleanup := openDB(t)
|
||||
// defer cleanup()
|
||||
// db.reactions.flushInterval = 10 * time.Millisecond
|
||||
reactions := db.Reactions()
|
||||
user := "masha"
|
||||
date := time.Now()
|
||||
|
||||
// reactions := db.Reactions()
|
||||
// author := "bob@d2.io"
|
||||
// date := time.Now()
|
||||
t.Run("empty reactions", func(t *testing.T) {
|
||||
list, err := reactions.OfUser("newuser", 10, 0)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, list)
|
||||
})
|
||||
|
||||
// t.Run("increment new reaction", func(t *testing.T) {
|
||||
// err := reactions.UpdateReference(author, date, "d1.com", "👍", 1)
|
||||
// require.NoError(t, err)
|
||||
// })
|
||||
t.Run("single reaction", func(t *testing.T) {
|
||||
err := reactions.Add(user, "bob", "d2.io", date, "👍", []byte("sig123"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// t.Run("increment existing multiple times", func(t *testing.T) {
|
||||
// require.NoError(t, reactions.UpdateReference(author, date, "d1.com", "👍", 2))
|
||||
// require.NoError(t, reactions.UpdateReference(author, date, "d1.com", "👍", 3))
|
||||
// })
|
||||
list, err := reactions.OfUser(user, 10, 0)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 1)
|
||||
require.Equal(t, "bob", list[0].Author)
|
||||
require.Equal(t, "d2.io", list[0].Domain)
|
||||
require.Equal(t, "👍", list[0].Emoji)
|
||||
})
|
||||
|
||||
// t.Run("decrement partial", func(t *testing.T) {
|
||||
// require.NoError(t, reactions.UpdateReference(author, date, "d1.com", "👍", -2))
|
||||
// })
|
||||
// }
|
||||
t.Run("multiple reactions", func(t *testing.T) {
|
||||
err := reactions.Add(user, "alice", "d3.net", date.Add(time.Second), "🔥", []byte("sig456"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// func TestReactions_ListReactions(t *testing.T) {
|
||||
// db, cleanup := openDB(t)
|
||||
// defer cleanup()
|
||||
// db.reactions.flushInterval = 10 * time.Millisecond
|
||||
list, err := reactions.OfUser(user, 10, 0)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 2)
|
||||
})
|
||||
|
||||
// reactions := db.Reactions()
|
||||
// author := "bob@d2.io"
|
||||
// date := time.Now()
|
||||
t.Run("pagination limit", func(t *testing.T) {
|
||||
list, err := reactions.OfUser(user, 1, 0)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 1)
|
||||
})
|
||||
|
||||
// t.Run("empty reactions", func(t *testing.T) {
|
||||
// list, err := reactions.ListReactions(author, date)
|
||||
// require.NoError(t, err)
|
||||
// require.Empty(t, list)
|
||||
// })
|
||||
t.Run("pagination offset", func(t *testing.T) {
|
||||
list, err := reactions.OfUser(user, 10, 1)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 1)
|
||||
})
|
||||
|
||||
// t.Run("single emoji after flush", func(t *testing.T) {
|
||||
// require.NoError(t, reactions.UpdateReference(author, date, "d1.com", "👍", 5))
|
||||
// time.Sleep(50 * time.Millisecond)
|
||||
t.Run("user isolation", func(t *testing.T) {
|
||||
err := reactions.Add("petya", "bob", "d2.io", date, "👍", []byte("sig777"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// list, err := reactions.ListReactions(author, date)
|
||||
// require.NoError(t, err)
|
||||
// require.Len(t, list, 1)
|
||||
// require.Equal(t, "👍", list[0].Emoji)
|
||||
// require.Equal(t, uint64(5), list[0].Count)
|
||||
// })
|
||||
mashaList, err := reactions.OfUser("masha", 10, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
// t.Run("multiple emojis from different domains", func(t *testing.T) {
|
||||
// require.NoError(t, reactions.UpdateReference(author, date, "d1.com", "🔥", 3))
|
||||
// require.NoError(t, reactions.UpdateReference(author, date, "d2.io", "🔥", 2))
|
||||
// require.NoError(t, reactions.UpdateReference(author, date, "d1.com", "❤️", 1))
|
||||
// time.Sleep(50 * time.Millisecond)
|
||||
petyaList, err := reactions.OfUser("petya", 10, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
// list, err := reactions.ListReactions(author, date)
|
||||
// require.NoError(t, err)
|
||||
require.Len(t, mashaList, 2)
|
||||
require.Len(t, petyaList, 1)
|
||||
})
|
||||
}
|
||||
|
||||
// counts := make(map[string]uint64)
|
||||
// for _, r := range list {
|
||||
// counts[r.Emoji] = r.Count
|
||||
// }
|
||||
// require.Equal(t, uint64(5), counts["👍"])
|
||||
// require.Equal(t, uint64(5), counts["🔥"])
|
||||
// require.Equal(t, uint64(1), counts["❤️"])
|
||||
// })
|
||||
// }
|
||||
func TestReactions_ToContent(t *testing.T) {
|
||||
db, cleanup := openDB(t)
|
||||
defer cleanup()
|
||||
|
||||
// func TestReactions_AfterClose(t *testing.T) {
|
||||
// path := t.TempDir() + "/db"
|
||||
// db, err := New(path)
|
||||
// require.NoError(t, err)
|
||||
r := db.Reactions()
|
||||
author := "bob"
|
||||
domain := "d2.io"
|
||||
date := time.Now()
|
||||
|
||||
// reactions := db.Reactions()
|
||||
// err = reactions.SetAllowed("masha@d1.com", time.Now(), []string{"👍"})
|
||||
// require.NoError(t, err)
|
||||
t.Run("empty reactions", func(t *testing.T) {
|
||||
list, err := r.ToContent(author, domain, date, 10, 0)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, list)
|
||||
})
|
||||
|
||||
// require.NoError(t, db.Close())
|
||||
t.Run("reactions from multiple users", func(t *testing.T) {
|
||||
require.NoError(t, r.Add("masha", author, domain, date, "👍", []byte("sig1")))
|
||||
require.NoError(t, r.Add("petya", author, domain, date, "🔥", []byte("sig2")))
|
||||
require.NoError(t, r.Add("alice", author, domain, date, "👍", []byte("sig3")))
|
||||
|
||||
// require.NotPanics(t, func() {
|
||||
// reactions.SetAllowed("masha@d1.com", time.Now(), []string{"👍"})
|
||||
// reactions.Allowed("masha@d1.com", time.Now())
|
||||
// reactions.AddExternal("masha@d1.com", "d1.com", "bob@d2.io", time.Now(), "👍", nil)
|
||||
// reactions.RemoveExternal("masha@d1.com", "d1.com", "bob@d2.io", time.Now(), "👍")
|
||||
// reactions.ToExternal("d1.com", "bob@d2.io", time.Now())
|
||||
// reactions.UpdateReference("bob@d2.io", time.Now(), "d1.com", "👍", 1)
|
||||
// reactions.ListReactions("bob@d2.io", time.Now())
|
||||
// })
|
||||
// }
|
||||
list, err := r.ToContent(author, domain, date, 10, 0)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 3)
|
||||
})
|
||||
|
||||
t.Run("pagination", func(t *testing.T) {
|
||||
list, err := r.ToContent(author, domain, date, 2, 0)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 2)
|
||||
|
||||
list, err = r.ToContent(author, domain, date, 10, 2)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 1)
|
||||
})
|
||||
|
||||
t.Run("wrong domain returns empty", func(t *testing.T) {
|
||||
list, err := r.ToContent(author, "unknown.com", date, 10, 0)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, list)
|
||||
})
|
||||
|
||||
t.Run("wrong author returns empty", func(t *testing.T) {
|
||||
list, err := r.ToContent("unknown", domain, date, 10, 0)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, list)
|
||||
})
|
||||
|
||||
t.Run("signature preserved", func(t *testing.T) {
|
||||
list, err := r.ToContent(author, domain, date, 10, 0)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 3)
|
||||
|
||||
sigs := make(map[string][]byte)
|
||||
for _, r := range list {
|
||||
sigs[r.User] = r.Signature
|
||||
}
|
||||
require.Equal(t, []byte("sig1"), sigs["masha"])
|
||||
require.Equal(t, []byte("sig2"), sigs["petya"])
|
||||
require.Equal(t, []byte("sig3"), sigs["alice"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestReactions_UpdateReference(t *testing.T) {
|
||||
db, cleanup := openDB(t)
|
||||
defer cleanup()
|
||||
db.reactions.flushInterval = 10 * time.Millisecond
|
||||
|
||||
reactions := db.Reactions()
|
||||
owner := "bob"
|
||||
date := time.Now()
|
||||
|
||||
t.Run("increment new reaction", func(t *testing.T) {
|
||||
err := reactions.UpdateReference(owner, date, "d1.com", "👍", 1)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("increment existing multiple times", func(t *testing.T) {
|
||||
require.NoError(t, reactions.UpdateReference(owner, date, "d1.com", "👍", 2))
|
||||
require.NoError(t, reactions.UpdateReference(owner, date, "d1.com", "👍", 3))
|
||||
})
|
||||
|
||||
t.Run("decrement partial", func(t *testing.T) {
|
||||
require.NoError(t, reactions.UpdateReference(owner, date, "d1.com", "👍", -2))
|
||||
})
|
||||
|
||||
t.Run("decrement below zero clamps to zero", func(t *testing.T) {
|
||||
require.NoError(t, reactions.UpdateReference(owner, date, "d3.net", "🔥", 1))
|
||||
require.NoError(t, reactions.UpdateReference(owner, date, "d3.net", "🔥", -10))
|
||||
})
|
||||
}
|
||||
|
||||
func TestReactions_ListReactions(t *testing.T) {
|
||||
db, cleanup := openDB(t)
|
||||
defer cleanup()
|
||||
db.reactions.flushInterval = 10 * time.Millisecond
|
||||
|
||||
reactions := db.Reactions()
|
||||
owner := "bob"
|
||||
date := time.Now()
|
||||
|
||||
t.Run("empty reactions", func(t *testing.T) {
|
||||
list, err := reactions.ListReactions(owner, date)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, list)
|
||||
})
|
||||
|
||||
t.Run("single emoji after flush", func(t *testing.T) {
|
||||
require.NoError(t, reactions.UpdateReference(owner, date, "d1.com", "👍", 5))
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
list, err := reactions.ListReactions(owner, date)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 1)
|
||||
require.Equal(t, "👍", list[0].Emoji)
|
||||
require.Equal(t, uint64(5), list[0].Count)
|
||||
})
|
||||
|
||||
t.Run("multiple emojis from different domains", func(t *testing.T) {
|
||||
require.NoError(t, reactions.UpdateReference(owner, date, "d1.com", "🔥", 3))
|
||||
require.NoError(t, reactions.UpdateReference(owner, date, "d2.io", "🔥", 2))
|
||||
require.NoError(t, reactions.UpdateReference(owner, date, "d1.com", "❤️", 1))
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
list, err := reactions.ListReactions(owner, date)
|
||||
require.NoError(t, err)
|
||||
|
||||
counts := make(map[string]uint64)
|
||||
for _, r := range list {
|
||||
counts[r.Emoji] = r.Count
|
||||
}
|
||||
require.Equal(t, uint64(5), counts["👍"])
|
||||
require.Equal(t, uint64(5), counts["🔥"])
|
||||
require.Equal(t, uint64(1), counts["❤️"])
|
||||
})
|
||||
|
||||
t.Run("decrement to zero removes emoji", func(t *testing.T) {
|
||||
owner2 := "test-delete"
|
||||
date2 := time.Now()
|
||||
require.NoError(t, reactions.UpdateReference(owner2, date2, "d1.com", "👍", 1))
|
||||
require.NoError(t, reactions.UpdateReference(owner2, date2, "d1.com", "👍", -1))
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
list, err := reactions.ListReactions(owner2, date2)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, list)
|
||||
})
|
||||
}
|
||||
|
||||
func TestReactions_AfterClose(t *testing.T) {
|
||||
path := t.TempDir() + "/db"
|
||||
db, err := New(path)
|
||||
require.NoError(t, err)
|
||||
|
||||
reactions := db.Reactions()
|
||||
err = reactions.SetAllowed("masha", time.Now(), []string{"👍"})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, db.Close())
|
||||
|
||||
require.NotPanics(t, func() {
|
||||
reactions.SetAllowed("masha", time.Now(), []string{"👍"})
|
||||
reactions.Allowed("masha", time.Now())
|
||||
reactions.Add("masha", "bob", "d2.io", time.Now(), "👍", nil)
|
||||
reactions.Remove("masha", "bob", "d2.io", time.Now(), "👍")
|
||||
reactions.OfUser("masha", 10, 0)
|
||||
reactions.ToContent("bob", "d2.io", time.Now(), 10, 0)
|
||||
reactions.UpdateReference("bob", time.Now(), "d1.com", "👍", 1)
|
||||
reactions.ListReactions("bob", time.Now())
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user