covered scenario of last message deletion and updating of content on dialogue view and deletion of dialog if all messages deleted
This commit is contained in:
+37
-36
@@ -23,7 +23,7 @@ type DB interface {
|
||||
Chat() Chat
|
||||
Posts() Posts
|
||||
Reactions() Reactions
|
||||
Comments() Comments
|
||||
// Comments() Comments
|
||||
|
||||
Close() error
|
||||
}
|
||||
@@ -127,7 +127,7 @@ type Message struct {
|
||||
type Posts interface {
|
||||
Create(post Post) error
|
||||
Get(author string, date time.Time) (Post, error)
|
||||
List(user string, limit, offset int) ([]Post, error)
|
||||
List(author string, limit, offset int) ([]Post, error)
|
||||
Update(post Post) error
|
||||
Delete(author string, date time.Time) error
|
||||
}
|
||||
@@ -143,14 +143,15 @@ type Post struct {
|
||||
}
|
||||
|
||||
type Reactions interface {
|
||||
SetAllowed(typ string, id uuid.UUID, emojis []string) error
|
||||
Allowed(typ string, id uuid.UUID) ([]string, error)
|
||||
AddExternal(typ string, id uuid.UUID, domain, user, emoji string, sig []byte) error
|
||||
RemoveExternal(typ string, id uuid.UUID, domain, user, emoji string) error
|
||||
ToExternal(typ string, id uuid.UUID, domain string) ([]Reaction, error)
|
||||
SetAllowed(author string, date time.Time, emojis []string) error
|
||||
Allowed(author string, date time.Time) ([]string, error)
|
||||
|
||||
UpdateReference(typ string, id uuid.UUID, domain, emoji string, delta int) error
|
||||
ListReactions(typ string, id uuid.UUID) ([]ReactionCounter, 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)
|
||||
|
||||
UpdateReference(author string, date time.Time, domain, emoji string, delta int) error
|
||||
ListReactions(author string, date time.Time) ([]ReactionCounter, error)
|
||||
}
|
||||
|
||||
type ReactionCounter struct {
|
||||
@@ -164,37 +165,37 @@ type Reaction struct {
|
||||
Signature []byte `json:"signature"`
|
||||
}
|
||||
|
||||
type Comments interface {
|
||||
Enable(typ string, id uuid.UUID) error
|
||||
Disable(typ string, id uuid.UUID) error
|
||||
AddExternal(comment Comment) (uuid.UUID, error)
|
||||
RemoveExternal(typ, user string, commentID, targetID uuid.UUID) error
|
||||
// type Comments interface {
|
||||
// Enable(typ string, id uuid.UUID) error
|
||||
// Disable(typ string, id uuid.UUID) error
|
||||
// AddExternal(comment Comment) (uuid.UUID, error)
|
||||
// RemoveExternal(typ, user string, commentID, targetID uuid.UUID) error
|
||||
|
||||
UpdateReference(typ string, id uuid.UUID, lastAt time.Time, domain string, delta int) error
|
||||
ListReferences(typ string, id uuid.UUID, limit, offset int) ([]CommentReference, error)
|
||||
}
|
||||
// UpdateReference(typ string, id uuid.UUID, lastAt time.Time, domain string, delta int) error
|
||||
// ListReferences(typ string, id uuid.UUID, limit, offset int) ([]CommentReference, error)
|
||||
// }
|
||||
|
||||
type Comment struct {
|
||||
ID uuid.UUID `json:"id" gob:"-"`
|
||||
Target CommentTarget `json:"target" gob:"1"`
|
||||
Author string `json:"author" gob:"-"`
|
||||
Domain string `json:"domain" gob:"2"`
|
||||
Body string `json:"body" gob:"3"`
|
||||
Date time.Time `json:"date" gob:"-"`
|
||||
References []CommentReference `json:"referencess" gob:"-"`
|
||||
Signature []byte `json:"signature" gob:"4"`
|
||||
}
|
||||
// type Comment struct {
|
||||
// ID uuid.UUID `json:"id" gob:"-"`
|
||||
// Target CommentTarget `json:"target" gob:"1"`
|
||||
// Author string `json:"author" gob:"-"`
|
||||
// Domain string `json:"domain" gob:"2"`
|
||||
// Body string `json:"body" gob:"3"`
|
||||
// Date time.Time `json:"date" gob:"-"`
|
||||
// References []CommentReference `json:"referencess" gob:"-"`
|
||||
// Signature []byte `json:"signature" gob:"4"`
|
||||
// }
|
||||
|
||||
type CommentTarget struct {
|
||||
Type string `json:"type" gob:"-"`
|
||||
ID uuid.UUID `json:"id" gob:"-"`
|
||||
}
|
||||
// type CommentTarget struct {
|
||||
// Type string `json:"type" gob:"-"`
|
||||
// ID uuid.UUID `json:"id" gob:"-"`
|
||||
// }
|
||||
|
||||
type CommentReference struct {
|
||||
Domain string `json:"domain"`
|
||||
Count int `json:"count"`
|
||||
LastAt time.Time `json:"lastAt"`
|
||||
}
|
||||
// type CommentReference struct {
|
||||
// Domain string `json:"domain"`
|
||||
// Count int `json:"count"`
|
||||
// LastAt time.Time `json:"lastAt"`
|
||||
// }
|
||||
|
||||
// Next modules to support:
|
||||
//
|
||||
|
||||
@@ -161,6 +161,7 @@ func (c *chat) Delete(user, contact string, date time.Time) error {
|
||||
ts := formatTime(date)
|
||||
metaKey := key(user, prefixChatMeta, contact, ts)
|
||||
attachKey := key(user, prefixChatAttach, contact, ts)
|
||||
dialogKey := key(user, prefixChatDialog, contact)
|
||||
|
||||
_, err := c.db.Get(metaKey, nil)
|
||||
if err != nil {
|
||||
@@ -176,6 +177,41 @@ func (c *chat) Delete(user, contact string, date time.Time) error {
|
||||
batch.Delete(metaKey)
|
||||
batch.Delete(attachKey)
|
||||
|
||||
prefix := key(user, prefixChatMeta, contact)
|
||||
iter := c.db.NewIterator(util.BytesPrefix(prefix), nil)
|
||||
|
||||
var latest database.Message
|
||||
for iter.Next() {
|
||||
var msg database.Message
|
||||
if gob.NewDecoder(bytes.NewReader(iter.Value())).Decode(&msg) == nil {
|
||||
if msg.Date.Equal(date) {
|
||||
continue
|
||||
}
|
||||
if msg.Date.After(latest.Date) {
|
||||
latest = msg
|
||||
}
|
||||
}
|
||||
}
|
||||
iter.Release()
|
||||
|
||||
if latest.Date.IsZero() {
|
||||
batch.Delete(dialogKey)
|
||||
} else {
|
||||
var prev database.Dialogue
|
||||
existing, err := c.db.Get(dialogKey, nil)
|
||||
if err == nil {
|
||||
gob.NewDecoder(bytes.NewReader(existing)).Decode(&prev)
|
||||
}
|
||||
prev.LastDate = latest.Date
|
||||
prev.LastContent = latest.Content
|
||||
|
||||
var dialogBuf bytes.Buffer
|
||||
if err := gob.NewEncoder(&dialogBuf).Encode(prev); err != nil {
|
||||
return fmt.Errorf("encode dialogue for %q: %w", user, err)
|
||||
}
|
||||
batch.Put(dialogKey, dialogBuf.Bytes())
|
||||
}
|
||||
|
||||
if err = c.db.Write(batch, woSync); err != nil {
|
||||
return fmt.Errorf("delete message for %q: %w", user, err)
|
||||
}
|
||||
|
||||
@@ -307,6 +307,68 @@ func TestChat_Delete(t *testing.T) {
|
||||
err := chat.Delete(user, contact, now)
|
||||
require.ErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
|
||||
t.Run("delete last message updates dialogue", func(t *testing.T) {
|
||||
contact := "dialog-update@test.com"
|
||||
first := time.Now()
|
||||
second := first.Add(time.Second)
|
||||
|
||||
err := chat.Put(user, database.Message{
|
||||
From: contact,
|
||||
To: user,
|
||||
Content: []byte("first"),
|
||||
Date: first,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = chat.Put(user, database.Message{
|
||||
From: contact,
|
||||
To: user,
|
||||
Content: []byte("second"),
|
||||
Date: second,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Delete the last message.
|
||||
err = chat.Delete(user, contact, second)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Dialogue should now show the first message.
|
||||
dialogues, err := chat.Dialogues(user, 10, time.Time{})
|
||||
require.NoError(t, err)
|
||||
var found bool
|
||||
for _, d := range dialogues {
|
||||
if d.Contact == contact {
|
||||
require.Equal(t, []byte("first"), d.LastContent)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
require.True(t, found, "dialogue should still exist")
|
||||
})
|
||||
|
||||
t.Run("delete only message removes dialogue", func(t *testing.T) {
|
||||
contact := "remove-dialog@test.com"
|
||||
msgTime := time.Now()
|
||||
|
||||
err := chat.Put(user, database.Message{
|
||||
From: contact,
|
||||
To: user,
|
||||
Content: []byte("only"),
|
||||
Date: msgTime,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Delete the only message.
|
||||
err = chat.Delete(user, contact, msgTime)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Dialogue should be gone.
|
||||
dialogues, err := chat.Dialogues(user, 10, time.Time{})
|
||||
require.NoError(t, err)
|
||||
for _, d := range dialogues {
|
||||
require.NotEqual(t, contact, d.Contact)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestChat_AfterClose(t *testing.T) {
|
||||
|
||||
@@ -65,8 +65,8 @@ func (p *posts) Get(author string, date time.Time) (database.Post, error) {
|
||||
return post, nil
|
||||
}
|
||||
|
||||
func (p *posts) List(user string, limit, offset int) ([]database.Post, error) {
|
||||
prefix := key(user, prefixPostMeta)
|
||||
func (p *posts) List(author string, limit, offset int) ([]database.Post, error) {
|
||||
prefix := key(author, prefixPostMeta)
|
||||
|
||||
iter := p.db.NewIterator(util.BytesPrefix(prefix), nil)
|
||||
defer iter.Release()
|
||||
@@ -76,7 +76,7 @@ func (p *posts) List(user string, limit, offset int) ([]database.Post, error) {
|
||||
for iter.Next() {
|
||||
var post database.Post
|
||||
if err := gob.NewDecoder(bytes.NewReader(iter.Value())).Decode(&post); err != nil {
|
||||
return nil, fmt.Errorf("decode post for %q: %w", user, err)
|
||||
return nil, fmt.Errorf("decode post for %q: %w", author, err)
|
||||
}
|
||||
|
||||
if skipped < offset {
|
||||
|
||||
Reference in New Issue
Block a user