fix(mail): relatorios DMARC nao mostravam o clipe de anexo
has_attachments so era definido para mensagens multipart. Um relatorio agregado DMARC do Google e de parte unica: o corpo E o anexo, um application/zip sem qualquer parte de texto. Ficava guardado como nao tendo anexos, ao lado dos relatorios TLS-RPT do mesmo remetente e na mesma pasta, que trazem uma linha de texto e por isso sao multipart/mixed e apareciam marcados. A regra passa a olhar tambem para a parte de topo: uma disposicao explicita decide (um .txt enviado como a mensagem inteira e anexo, uma parte inline nao e), e sem ela conta como anexo tudo o que nao seja texto nem multipart -- um zip, um PDF, uma mensagem reencaminhada. As mensagens ja guardadas mantinham a marca antiga, e sao justamente aquelas que o utilizador esta a olhar. Ha um backfill que as re-le uma vez, em segundo plano, sem atrasar o arranque. So examina as que estao marcadas como sem anexos: a regra antiga nunca inventou um anexo que nao existisse, portanto uma marca verdadeira nao precisa de segunda opiniao e a varredura fica do tamanho do defeito. A tabela backfills guarda o marcador para nao repetir a cada arranque. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7f8d9b7ff1
commit
c06691a056
7 files changed
+279
-6
No files matched your search
@@ -0,0 +1,87 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
// AttachmentBackfill repairs has_attachments on messages already stored.
|
||||
//
|
||||
// The flag was only ever set for multipart messages, so a single-part
|
||||
// attachment - a DMARC aggregate report, whose entire body is a zip - was
|
||||
// filed as having none, and showed up without a paperclip beside TLS-RPT
|
||||
// reports from the same sender that happened to carry a text part.
|
||||
type AttachmentBackfill struct {
|
||||
pool *pgxpool.Pool
|
||||
}
|
||||
|
||||
func NewAttachmentBackfill(pool *pgxpool.Pool) *AttachmentBackfill {
|
||||
return &AttachmentBackfill{pool: pool}
|
||||
}
|
||||
|
||||
// Done reports whether this repair has already run.
|
||||
func (b *AttachmentBackfill) Done(ctx context.Context, name string) (bool, error) {
|
||||
var exists bool
|
||||
err := b.pool.QueryRow(ctx,
|
||||
`SELECT EXISTS (SELECT 1 FROM backfills WHERE name = $1)`, name).Scan(&exists)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("read backfill marker %s: %w", name, err)
|
||||
}
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
func (b *AttachmentBackfill) MarkDone(ctx context.Context, name, detail string) error {
|
||||
_, err := b.pool.Exec(ctx,
|
||||
`INSERT INTO backfills (name, detail) VALUES ($1, $2) ON CONFLICT (name) DO NOTHING`,
|
||||
name, detail)
|
||||
if err != nil {
|
||||
return fmt.Errorf("record backfill marker %s: %w", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Candidate is one message worth re-reading.
|
||||
type Candidate struct {
|
||||
ID uuid.UUID
|
||||
BlobID uuid.UUID
|
||||
}
|
||||
|
||||
// Candidates lists messages currently flagged as having no attachment.
|
||||
//
|
||||
// Only those: the old rule never claimed an attachment that was not there, so
|
||||
// a true flag needs no second opinion and the scan stays as small as the bug.
|
||||
func (b *AttachmentBackfill) Candidates(ctx context.Context, limit int) ([]Candidate, error) {
|
||||
rows, err := b.pool.Query(ctx, `
|
||||
SELECT id, blob_id FROM email_messages
|
||||
WHERE has_attachments = false AND expunged_at IS NULL
|
||||
ORDER BY received_at DESC
|
||||
LIMIT $1
|
||||
`, limit)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list attachment backfill candidates: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
out := []Candidate{}
|
||||
for rows.Next() {
|
||||
var c Candidate
|
||||
if err := rows.Scan(&c.ID, &c.BlobID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, c)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// SetHasAttachments records the recomputed flag.
|
||||
func (b *AttachmentBackfill) SetHasAttachments(ctx context.Context, messageID uuid.UUID) error {
|
||||
_, err := b.pool.Exec(ctx,
|
||||
`UPDATE email_messages SET has_attachments = true WHERE id = $1`, messageID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("set has_attachments for %s: %w", messageID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user