All checks were successful
ci / build (push) Successful in 1m49s
Changed the API response to embed Payload as a JSON object using json.RawMessage instead of returning it as a JSON-encoded string inside the JSON response. Added MetricResponse type with ToResponse converter method. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
113 lines
3.3 KiB
Go
113 lines
3.3 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
|
|
}
|
|
|
|
// MetricResponse is the API response type with Payload as embedded JSON object
|
|
type MetricResponse struct {
|
|
ID uint `json:"id"`
|
|
Organization string `json:"organization"`
|
|
Repository string `json:"repository"`
|
|
Workflow string `json:"workflow"`
|
|
Job string `json:"job"`
|
|
RunID string `json:"run_id"`
|
|
ReceivedAt time.Time `json:"received_at"`
|
|
Payload json.RawMessage `json:"payload"`
|
|
}
|
|
|
|
// ToResponse converts a Metric to a MetricResponse with Payload as JSON object
|
|
func (m *Metric) ToResponse() MetricResponse {
|
|
return MetricResponse{
|
|
ID: m.ID,
|
|
Organization: m.Organization,
|
|
Repository: m.Repository,
|
|
Workflow: m.Workflow,
|
|
Job: m.Job,
|
|
RunID: m.RunID,
|
|
ReceivedAt: m.ReceivedAt,
|
|
Payload: json.RawMessage(m.Payload),
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// GetMetricsByWorkflowJob retrieves all metrics for a specific workflow and job
|
|
func (s *Store) GetMetricsByWorkflowJob(org, repo, workflow, job string) ([]Metric, error) {
|
|
var metrics []Metric
|
|
result := s.db.Where("organization = ? AND repository = ? AND workflow = ? AND job = ?", org, repo, workflow, job).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()
|
|
}
|