fix: align mailbox unread and bulk actions

This commit is contained in:
zxyszx
2026-08-02 11:23:44 +08:00
parent 21be227d7b
commit 50d7c091bf
5 changed files with 137 additions and 48 deletions
+35
View File
@@ -1379,6 +1379,34 @@ func TestUserCanSelectMultipleMailboxes(t *testing.T) {
t.Fatalf("mailboxes were not bound to one user: primary=%s secondary=%s", primary.UserID, secondary.UserID)
}
ctx := context.Background()
now := a.now().UTC().Format(time.RFC3339Nano)
primaryInboxID, err := a.ensureFolder(ctx, primary.ID, "Inbox")
if err != nil {
t.Fatal(err)
}
primaryArchiveID, err := a.ensureFolder(ctx, primary.ID, "Archive")
if err != nil {
t.Fatal(err)
}
secondaryInboxID, err := a.ensureFolder(ctx, secondary.ID, "Inbox")
if err != nil {
t.Fatal(err)
}
insertMessage := func(id, mailboxID, folderID, subject string, read int) {
t.Helper()
if _, err := a.db.ExecContext(ctx, `INSERT INTO messages(id,mailbox_id,folder_id,recipient_addr,message_uid,message_id,subject,from_addr,from_name,to_addrs,cc_addrs,bcc_addrs,sent_at,received_at,snippet,body_text,body_html,is_read,is_starred,has_attachments,size_bytes,created_at,updated_at)
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
id, mailboxID, folderID, "", id+"-uid", "<"+id+"@example.test>", subject, "sender@example.test", "", jsonEncode([]string{"recipient@example.test"}), "[]", "[]", now, now, subject, "", "", read, 0, 0, 0, now, now); err != nil {
t.Fatal(err)
}
}
insertMessage("msg_multi_primary_unread_1", primary.ID, primaryInboxID, "primary unread one", 0)
insertMessage("msg_multi_primary_unread_2", primary.ID, primaryInboxID, "primary unread two", 0)
insertMessage("msg_multi_primary_read", primary.ID, primaryInboxID, "primary read", 1)
insertMessage("msg_multi_primary_archived", primary.ID, primaryArchiveID, "primary archived unread", 0)
insertMessage("msg_multi_secondary_unread", secondary.ID, secondaryInboxID, "secondary unread", 0)
userClient := &testClient{t: t, server: ts}
if code := userClient.do("POST", "/api/auth/login", map[string]string{"email": primary.Address, "password": "Password123!"}, &login); code != http.StatusOK {
t.Fatalf("user login=%d", code)
@@ -1389,6 +1417,13 @@ func TestUserCanSelectMultipleMailboxes(t *testing.T) {
if code := userClient.do("GET", "/api/mail/mailboxes", nil, &mine); code != http.StatusOK || len(mine.Items) != 2 {
t.Fatalf("my mailboxes code=%d items=%d", code, len(mine.Items))
}
unreadByAddress := map[string]int{}
for _, item := range mine.Items {
unreadByAddress[item.Address] = item.UnreadCount
}
if unreadByAddress[primary.Address] != 2 || unreadByAddress[secondary.Address] != 1 {
t.Fatalf("mailbox unread counts=%+v, want %s=2 %s=1", unreadByAddress, primary.Address, secondary.Address)
}
if code := userClient.do("GET", "/api/mail/folders?mailboxId="+secondary.ID, nil, nil); code != http.StatusOK {
t.Fatalf("folders for selected mailbox code=%d", code)
}
+6 -2
View File
@@ -59,10 +59,14 @@ type storedMessage struct {
func (a *App) handleMyMailboxes(w http.ResponseWriter, r *http.Request) {
user := currentUser(r)
rows, err := a.db.QueryContext(r.Context(), `SELECT mb.id,mb.user_id,mb.domain_id,mb.local_part,mb.address,mb.display_name,mb.quota_mb,mb.status,mb.created_at
rows, err := a.db.QueryContext(r.Context(), `SELECT mb.id,mb.user_id,mb.domain_id,mb.local_part,mb.address,mb.display_name,mb.quota_mb,mb.status,mb.created_at,
COALESCE(SUM(CASE WHEN lower(f.name)='inbox' AND m.is_read=0 THEN 1 ELSE 0 END),0) AS unread_count
FROM mailboxes mb
JOIN domains d ON d.id=mb.domain_id
LEFT JOIN folders f ON f.mailbox_id=mb.id
LEFT JOIN messages m ON m.folder_id=f.id
WHERE mb.user_id=? AND mb.status='active' AND d.status='active'
GROUP BY mb.id,mb.user_id,mb.domain_id,mb.local_part,mb.address,mb.display_name,mb.quota_mb,mb.status,mb.created_at
ORDER BY mb.address`, user.ID)
if err != nil {
respondError(w, http.StatusInternalServerError, "failed to load mailboxes")
@@ -73,7 +77,7 @@ func (a *App) handleMyMailboxes(w http.ResponseWriter, r *http.Request) {
for rows.Next() {
var m Mailbox
var created string
if err := rows.Scan(&m.ID, &m.UserID, &m.DomainID, &m.LocalPart, &m.Address, &m.DisplayName, &m.QuotaMB, &m.Status, &created); err != nil {
if err := rows.Scan(&m.ID, &m.UserID, &m.DomainID, &m.LocalPart, &m.Address, &m.DisplayName, &m.QuotaMB, &m.Status, &created, &m.UnreadCount); err != nil {
respondError(w, http.StatusInternalServerError, "failed to scan mailboxes")
return
}
+1
View File
@@ -69,6 +69,7 @@ type Mailbox struct {
DisplayName string `json:"displayName"`
QuotaMB int `json:"quotaMb"`
Status string `json:"status"`
UnreadCount int `json:"unreadCount"`
CreatedAt time.Time `json:"createdAt"`
}