forgejo-runner-optimiser/internal/receiver/store.go
Manuel Ganter eb01c1c842
All checks were successful
ci / build (push) Successful in 1m49s
fix(receiver): return Payload as JSON object instead of string
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>
2026-02-06 15:22:27 +01:00

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()
}