feat: auditor sieht Mails ohne Tenant-Zuordnung
- auditor-Rolle sieht jetzt Mails wo tenant_id IS NULL und kein email_refs-Eintrag existiert (statt nur eigene Mails) - Neues storage.IsWithoutTenant() für effizienten Direktzugriff - Neues storage.GetAllIDsWithoutTenant() für Suche + ZIP-Export - Konsistente Prüfung in Search, GetMail, GetAttachment, GetRaw, Export Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -970,6 +970,55 @@ func (s *Store) StatsByTenant(ctx context.Context, tenantID *int64) (map[string]
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IsWithoutTenant reports whether a mail has no tenant assignment —
|
||||
// neither a direct tenant_id nor any email_refs entry.
|
||||
// Used by the auditor role to gate direct mail access.
|
||||
func (s *Store) IsWithoutTenant(ctx context.Context, id string) (bool, error) {
|
||||
if s.db == nil {
|
||||
return false, nil
|
||||
}
|
||||
var result bool
|
||||
err := s.db.QueryRow(ctx, `
|
||||
SELECT (e.tenant_id IS NULL)
|
||||
AND NOT EXISTS (SELECT 1 FROM email_refs r WHERE r.email_id = e.id)
|
||||
FROM emails e WHERE e.id = $1
|
||||
`, id).Scan(&result)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("storage: is without tenant: %w", err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetAllIDsWithoutTenant returns email IDs that have no tenant assignment.
|
||||
// Used for the auditor role: access to global (untenanted) mails only.
|
||||
func (s *Store) GetAllIDsWithoutTenant(ctx context.Context) ([]string, error) {
|
||||
if s.db == nil {
|
||||
return nil, nil
|
||||
}
|
||||
rows, err := s.db.Query(ctx, `
|
||||
SELECT e.id FROM emails e
|
||||
WHERE e.tenant_id IS NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM email_refs r WHERE r.email_id = e.id)
|
||||
ORDER BY e.received_at
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storage: get ids without tenant: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
var ids []string
|
||||
for rows.Next() {
|
||||
var id string
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids, rows.Err()
|
||||
}
|
||||
|
||||
// GetAllIDs returns all email IDs from the DB, or walks the store if no DB.
|
||||
func (s *Store) GetAllIDs(ctx context.Context) ([]string, error) {
|
||||
if s.db != nil {
|
||||
|
||||
Reference in New Issue
Block a user