diff --git a/apps/api/internal/app/app.go b/apps/api/internal/app/app.go index c8255de..88d4010 100644 --- a/apps/api/internal/app/app.go +++ b/apps/api/internal/app/app.go @@ -427,6 +427,9 @@ func (a *App) migrate(ctx context.Context) error { if err := a.migrateMessageAuthentication(ctx); err != nil { return err } + if err := a.rebuildHTMLOnlyMessageSnippets(ctx); err != nil { + return err + } if err := a.migrateUsersForTwoFactor(ctx); err != nil { return err } @@ -492,6 +495,47 @@ func (a *App) migrateMessageAuthentication(ctx context.Context) error { return nil } +func (a *App) rebuildHTMLOnlyMessageSnippets(ctx context.Context) error { + rows, err := a.db.QueryContext(ctx, `SELECT id,body_html,snippet FROM messages WHERE trim(body_text)='' AND body_html<>''`) + if err != nil { + return err + } + defer rows.Close() + type update struct { + id string + snippet string + } + updates := []update{} + for rows.Next() { + var id, bodyHTML, current string + if err := rows.Scan(&id, &bodyHTML, ¤t); err != nil { + return err + } + next := snippetFrom("", bodyHTML) + if next != current { + updates = append(updates, update{id: id, snippet: next}) + } + } + if err := rows.Err(); err != nil { + return err + } + if len(updates) == 0 { + return nil + } + now := a.now().UTC().Format(time.RFC3339Nano) + tx, err := a.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer tx.Rollback() + for _, item := range updates { + if _, err := tx.ExecContext(ctx, `UPDATE messages SET snippet=?,updated_at=? WHERE id=?`, item.snippet, now, item.id); err != nil { + return err + } + } + return tx.Commit() +} + func (a *App) migrateSendQueueMessageID(ctx context.Context) error { rows, err := a.db.QueryContext(ctx, `PRAGMA table_info(send_queue)`) if err != nil { diff --git a/apps/api/internal/app/app_test.go b/apps/api/internal/app/app_test.go index 5762065..21d4968 100644 --- a/apps/api/internal/app/app_test.go +++ b/apps/api/internal/app/app_test.go @@ -442,6 +442,46 @@ func TestParseMailAuthenticationResults(t *testing.T) { } } +func TestSnippetFromHTMLIgnoresStyleContent(t *testing.T) { + html := `
蓝钦AI 余额充值成功
` + got := snippetFrom("", html) + if strings.Contains(got, "body {") || strings.Contains(got, "margin:") || strings.Contains(got, "Hidden title") { + t.Fatalf("snippet kept non-content html text: %q", got) + } + if !strings.Contains(got, "蓝钦AI 余额充值成功") { + t.Fatalf("snippet missing body text: %q", got) + } +} + +func TestRebuildHTMLOnlyMessageSnippetsDropsStyleContent(t *testing.T) { + a := newTestApp(t) + ctx := context.Background() + _, mb := defaultAdminUserAndMailbox(t, a) + folderID, err := a.ensureFolder(ctx, mb.ID, "Sent") + if err != nil { + t.Fatal(err) + } + now := a.now().UTC().Format(time.RFC3339Nano) + bodyHTML := `hello readable body
` + 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(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, "msg_css_snippet", mb.ID, folderID, "", newID("uid"), "