All checks were successful
ci / build (push) Successful in 2m33s
Add a new receiver application under cmd/receiver that accepts metrics via HTTP POST and stores them in SQLite using GORM. The receiver expects GitHub Actions style execution context (org, repo, workflow, job, run_id). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
94 lines
2.7 KiB
Go
94 lines
2.7 KiB
Go
// ABOUTME: SQLite storage layer for metrics receiver using GORM.
|
|
// ABOUTME: Handles database initialization and metric storage/retrieval.
|
|
package receiver
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
)
|
|
|
|
// Metric represents a stored metric record in the database
|
|
type Metric struct {
|
|
ID uint `gorm:"primaryKey"`
|
|
Organization string `gorm:"index:idx_org_repo;not null"`
|
|
Repository string `gorm:"index:idx_org_repo;not null"`
|
|
Workflow string `gorm:"not null"`
|
|
Job string `gorm:"not null"`
|
|
RunID string `gorm:"index;not null"`
|
|
ReceivedAt time.Time `gorm:"index;not null"`
|
|
Payload string `gorm:"type:text;not null"` // JSON-encoded RunSummary
|
|
}
|
|
|
|
// Store handles SQLite storage for metrics using GORM
|
|
type Store struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewStore creates a new SQLite store at the given path
|
|
func NewStore(dbPath string) (*Store, error) {
|
|
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{
|
|
Logger: logger.Default.LogMode(logger.Silent),
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("opening database: %w", err)
|
|
}
|
|
|
|
if err := db.AutoMigrate(&Metric{}); err != nil {
|
|
return nil, fmt.Errorf("migrating schema: %w", err)
|
|
}
|
|
|
|
return &Store{db: db}, nil
|
|
}
|
|
|
|
// SaveMetric stores a metrics payload in the database
|
|
func (s *Store) SaveMetric(payload *MetricsPayload) (uint, error) {
|
|
summaryJSON, err := json.Marshal(payload.Summary)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("marshaling summary: %w", err)
|
|
}
|
|
|
|
metric := Metric{
|
|
Organization: payload.Execution.Organization,
|
|
Repository: payload.Execution.Repository,
|
|
Workflow: payload.Execution.Workflow,
|
|
Job: payload.Execution.Job,
|
|
RunID: payload.Execution.RunID,
|
|
ReceivedAt: time.Now().UTC(),
|
|
Payload: string(summaryJSON),
|
|
}
|
|
|
|
result := s.db.Create(&metric)
|
|
if result.Error != nil {
|
|
return 0, fmt.Errorf("inserting metric: %w", result.Error)
|
|
}
|
|
|
|
return metric.ID, nil
|
|
}
|
|
|
|
// GetMetricsByRunID retrieves all metrics for a specific run
|
|
func (s *Store) GetMetricsByRunID(runID string) ([]Metric, error) {
|
|
var metrics []Metric
|
|
result := s.db.Where("run_id = ?", runID).Order("received_at DESC").Find(&metrics)
|
|
return metrics, result.Error
|
|
}
|
|
|
|
// GetMetricsByRepository retrieves all metrics for a specific repository
|
|
func (s *Store) GetMetricsByRepository(org, repo string) ([]Metric, error) {
|
|
var metrics []Metric
|
|
result := s.db.Where("organization = ? AND repository = ?", org, repo).Order("received_at DESC").Find(&metrics)
|
|
return metrics, result.Error
|
|
}
|
|
|
|
// Close closes the database connection
|
|
func (s *Store) Close() error {
|
|
sqlDB, err := s.db.DB()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return sqlDB.Close()
|
|
}
|