This commit captures the current state before implementing the composable monorepo template system. Key changes included: Infrastructure: - Add CockroachDB provisioner adapter for database provisioning - Add Redis provisioner adapter for cache provisioning - Add build events system with PostgreSQL storage - Add WebSocket endpoint for real-time build progress Code agent improvements: - Fix Claude Code adapter to use default allowed tools instead of dangerously-skip-permissions - Add context-aware stream closing for cancellation support - Improve parser tests for edge cases Build system: - Add build event constants and metrics - Remove deprecated git_operations.go (replaced by pod_git_operations.go) - Add rollback logic for multi-step provisioning operations Documentation: - Add composable-monorepo feature documentation - Add DNS/Cloudflare service documentation - Update deployment and troubleshooting guides Cookbooks: - Add fullstack-app cookbook - Refactor landing-test with shared library Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
108 lines
3.0 KiB
Go
108 lines
3.0 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/orchard9/rdev/internal/domain"
|
|
"github.com/orchard9/rdev/internal/port"
|
|
)
|
|
|
|
// BuildEventRepository implements port.BuildEventStore using PostgreSQL.
|
|
type BuildEventRepository struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
// NewBuildEventRepository creates a new build event repository.
|
|
func NewBuildEventRepository(db *sql.DB) *BuildEventRepository {
|
|
return &BuildEventRepository{db: db}
|
|
}
|
|
|
|
// Ensure BuildEventRepository implements port.BuildEventStore at compile time.
|
|
var _ port.BuildEventStore = (*BuildEventRepository)(nil)
|
|
|
|
// buildEventRow is the database representation of a build event.
|
|
type buildEventRow struct {
|
|
ID string `db:"id"`
|
|
TaskID string `db:"task_id"`
|
|
ProjectID string `db:"project_id"`
|
|
Type string `db:"type"`
|
|
Sequence int64 `db:"sequence"`
|
|
Timestamp time.Time `db:"timestamp"`
|
|
Data []byte `db:"data"`
|
|
CreatedAt time.Time `db:"created_at"`
|
|
}
|
|
|
|
// Record stores a build event for later replay.
|
|
func (r *BuildEventRepository) Record(ctx context.Context, event *domain.BuildEvent) error {
|
|
dataBytes, err := json.Marshal(event.Data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = r.db.ExecContext(ctx, `
|
|
INSERT INTO build_events (id, task_id, project_id, type, sequence, timestamp, data)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
ON CONFLICT (id) DO NOTHING
|
|
`, event.ID, event.TaskID, event.ProjectID, event.Type, event.Sequence, event.Timestamp, dataBytes)
|
|
|
|
return err
|
|
}
|
|
|
|
// ListByTask retrieves events for a task, optionally after a sequence number.
|
|
func (r *BuildEventRepository) ListByTask(ctx context.Context, taskID string, afterSequence int64) ([]*domain.BuildEvent, error) {
|
|
rows, err := r.db.QueryContext(ctx, `
|
|
SELECT id, task_id, project_id, type, sequence, timestamp, data
|
|
FROM build_events
|
|
WHERE task_id = $1 AND sequence > $2
|
|
ORDER BY sequence ASC
|
|
`, taskID, afterSequence)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() { _ = rows.Close() }()
|
|
|
|
var events []*domain.BuildEvent
|
|
for rows.Next() {
|
|
var row buildEventRow
|
|
if err := rows.Scan(&row.ID, &row.TaskID, &row.ProjectID, &row.Type, &row.Sequence, &row.Timestamp, &row.Data); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var data domain.BuildEventData
|
|
if err := json.Unmarshal(row.Data, &data); err != nil {
|
|
// If unmarshal fails, use empty data
|
|
data = domain.BuildEventData{}
|
|
}
|
|
|
|
events = append(events, &domain.BuildEvent{
|
|
ID: row.ID,
|
|
TaskID: row.TaskID,
|
|
ProjectID: row.ProjectID,
|
|
Type: domain.BuildEventType(row.Type),
|
|
Sequence: row.Sequence,
|
|
Timestamp: row.Timestamp,
|
|
Data: data,
|
|
})
|
|
}
|
|
|
|
return events, rows.Err()
|
|
}
|
|
|
|
// Cleanup removes events older than the specified age.
|
|
func (r *BuildEventRepository) Cleanup(ctx context.Context, olderThan time.Duration) (int64, error) {
|
|
cutoff := time.Now().Add(-olderThan)
|
|
|
|
result, err := r.db.ExecContext(ctx, `
|
|
DELETE FROM build_events
|
|
WHERE created_at < $1
|
|
`, cutoff)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return result.RowsAffected()
|
|
}
|