Add paginated test history listing with disable option

Add GET /tests endpoint returning lightweight test summaries (grade,
score, domain, date) with pagination, using database-level JSON
extraction to avoid loading full report blobs. The feature can be
disabled with --disable-test-list flag. Frontend includes a new
/tests/ page with table view and a conditional "History" navbar link.

Fixes: https://github.com/happyDomain/happydeliver/issues/12
This commit is contained in:
nemunaire 2026-04-09 17:46:08 +07:00
commit 7422f6ed0a
12 changed files with 546 additions and 3 deletions

View file

@ -45,11 +45,21 @@ type Storage interface {
ReportExists(testID uuid.UUID) (bool, error)
UpdateReport(testID uuid.UUID, reportJSON []byte) error
DeleteOldReports(olderThan time.Time) (int64, error)
ListReportSummaries(offset, limit int) ([]ReportSummary, int64, error)
// Close closes the database connection
Close() error
}
// ReportSummary is a lightweight projection of Report for listing
type ReportSummary struct {
TestID uuid.UUID
Score int
Grade string
FromDomain string
CreatedAt time.Time
}
// DBStorage implements Storage using GORM
type DBStorage struct {
db *gorm.DB
@ -139,6 +149,47 @@ func (s *DBStorage) DeleteOldReports(olderThan time.Time) (int64, error) {
return result.RowsAffected, nil
}
// ListReportSummaries returns a paginated list of lightweight report summaries
func (s *DBStorage) ListReportSummaries(offset, limit int) ([]ReportSummary, int64, error) {
var total int64
if err := s.db.Model(&Report{}).Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("failed to count reports: %w", err)
}
if total == 0 {
return []ReportSummary{}, 0, nil
}
var selectExpr string
switch s.db.Dialector.Name() {
case "postgres":
selectExpr = `test_id, ` +
`(convert_from(report_json, 'UTF8')::jsonb->>'score')::int as score, ` +
`convert_from(report_json, 'UTF8')::jsonb->>'grade' as grade, ` +
`convert_from(report_json, 'UTF8')::jsonb->'dns_results'->>'from_domain' as from_domain, ` +
`created_at`
default: // sqlite
selectExpr = `test_id, ` +
`json_extract(report_json, '$.score') as score, ` +
`json_extract(report_json, '$.grade') as grade, ` +
`json_extract(report_json, '$.dns_results.from_domain') as from_domain, ` +
`created_at`
}
var summaries []ReportSummary
err := s.db.Model(&Report{}).
Select(selectExpr).
Order("created_at DESC").
Offset(offset).
Limit(limit).
Scan(&summaries).Error
if err != nil {
return nil, 0, fmt.Errorf("failed to list report summaries: %w", err)
}
return summaries, total, nil
}
// Close closes the database connection
func (s *DBStorage) Close() error {
sqlDB, err := s.db.DB()