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:
d1nch8g
2026-05-11 05:12:46 +07:00
parent c08a457cc1
commit 490a34856d
4 changed files with 138 additions and 39 deletions
+37 -36
View File
@@ -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:
//
+36
View File
@@ -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)
}
+62
View File
@@ -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) {
+3 -3
View File
@@ -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 {