cairn/internal/forgejo/sync.go

108 lines
2.8 KiB
Go

package forgejo
import (
"context"
"fmt"
"strings"
cairnapi "github.com/mattnite/cairn/internal/api"
"github.com/rs/zerolog/log"
"gorm.io/gorm"
"github.com/mattnite/cairn/internal/models"
)
// Sync handles bidirectional state synchronization between Cairn and Forgejo.
type Sync struct {
Client *Client
DB *gorm.DB
CairnURL string
}
// CreateIssueForCrashGroup creates a Forgejo issue for a new crash group.
func (s *Sync) CreateIssueForCrashGroup(ctx context.Context, group *cairnapi.CrashGroup, sampleTrace string) error {
if s.Client == nil {
return nil
}
repo, err := models.GetRepositoryByID(ctx, s.DB, group.RepositoryID)
if err != nil {
return fmt.Errorf("getting repository: %w", err)
}
crashGroupLink := fmt.Sprintf("%d", group.ID)
if s.CairnURL != "" {
crashGroupLink = fmt.Sprintf("[%d](%s/crashgroups/%d)", group.ID, strings.TrimRight(s.CairnURL, "/"), group.ID)
}
body := fmt.Sprintf(`## Crash Group
**Crash Group:** %s
**Fingerprint:** `+"`%s`"+`
**First seen:** %s
**Type:** %s
### Sample Stack Trace
`+"```"+`
%s
`+"```"+`
---
*Auto-created by [Cairn](/) — crash artifact aggregator*
`, crashGroupLink, group.Fingerprint, group.FirstSeenAt.Format("2006-01-02 15:04:05"), group.Title, sampleTrace)
issue, err := s.Client.CreateIssue(ctx, repo.Owner, repo.Name, CreateIssueRequest{
Title: "[Cairn] " + group.Title,
Body: body,
})
if err != nil {
return fmt.Errorf("creating issue: %w", err)
}
if err := s.DB.WithContext(ctx).
Model(&models.CrashGroup{}).
Where("id = ?", group.ID).
Update("forgejo_issue_id", issue.Number).Error; err != nil {
return err
}
issueURL := fmt.Sprintf("%s/%s/%s/issues/%d", strings.TrimRight(s.Client.BaseURL(), "/"), repo.Owner, repo.Name, issue.Number)
group.ForgejoIssueURL = &issueURL
return nil
}
// HandleIssueEvent processes a Forgejo issue webhook event for state sync.
func (s *Sync) HandleIssueEvent(ctx context.Context, event *WebhookEvent) error {
if event.Issue == nil {
return nil
}
// Only handle issues that start with [Cairn] prefix.
if !strings.HasPrefix(event.Issue.Title, "[Cairn] ") {
return nil
}
switch event.Action {
case "closed":
return s.DB.WithContext(ctx).
Model(&models.CrashGroup{}).
Where("forgejo_issue_id = ?", event.Issue.Number).
Update("status", "resolved").Error
case "reopened":
return s.DB.WithContext(ctx).
Model(&models.CrashGroup{}).
Where("forgejo_issue_id = ?", event.Issue.Number).
Update("status", "open").Error
}
return nil
}
// HandlePushEvent processes a push webhook event for commit enrichment.
func (s *Sync) HandlePushEvent(ctx context.Context, event *WebhookEvent) {
if event.Repo == nil || event.After == "" {
return
}
log.Info().Str("repo", event.Repo.FullName).Str("sha", event.After[:8]).Msg("Push event")
}