211 lines
6.1 KiB
Go
211 lines
6.1 KiB
Go
package event
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/rs/zerolog"
|
|
|
|
"github.com/vincentc-afk/gitea-notification-hub/internal/identity"
|
|
"github.com/vincentc-afk/gitea-notification-hub/internal/slack"
|
|
"github.com/vincentc-afk/gitea-notification-hub/internal/webhook"
|
|
)
|
|
|
|
// Processor handles processing of webhook events
|
|
type Processor struct {
|
|
resolver identity.Resolver
|
|
slack slack.Client
|
|
logger zerolog.Logger
|
|
}
|
|
|
|
// NewProcessor creates a new event processor
|
|
func NewProcessor(resolver identity.Resolver, slackClient slack.Client, logger zerolog.Logger) *Processor {
|
|
return &Processor{
|
|
resolver: resolver,
|
|
slack: slackClient,
|
|
logger: logger.With().Str("component", "processor").Logger(),
|
|
}
|
|
}
|
|
|
|
// ProcessPullRequest handles pull_request webhook events
|
|
func (p *Processor) ProcessPullRequest(ctx context.Context, event *webhook.PullRequestEvent) error {
|
|
logger := p.logger.With().
|
|
Int64("pr_number", event.Number).
|
|
Str("action", event.Action).
|
|
Str("repo", event.Repository.FullName).
|
|
Logger()
|
|
|
|
logger.Info().Msg("processing pull request event")
|
|
|
|
switch event.Action {
|
|
case "review_requested":
|
|
return p.handleReviewRequested(ctx, event, logger)
|
|
case "opened":
|
|
return p.handleOpened(ctx, event, logger)
|
|
case "closed":
|
|
return p.handleClosed(ctx, event, logger)
|
|
default:
|
|
logger.Debug().Msg("ignoring pull_request action")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// handleReviewRequested processes review_requested events
|
|
func (p *Processor) handleReviewRequested(ctx context.Context, event *webhook.PullRequestEvent, logger zerolog.Logger) error {
|
|
logger.Info().Msg("processing review_requested event")
|
|
|
|
var reviewer *webhook.GiteaUser
|
|
|
|
// Approach 1: event.RequestedReviewers array (standard)
|
|
if len(event.RequestedReviewers) > 0 {
|
|
reviewer = &event.RequestedReviewers[0]
|
|
logger.Info().
|
|
Int("count", len(event.RequestedReviewers)).
|
|
Str("reviewer", reviewer.Login).
|
|
Msg("found reviewer in RequestedReviewers array")
|
|
} else if event.RequestedReviewer != nil {
|
|
// Approach 2: singular field (edge case)
|
|
reviewer = event.RequestedReviewer
|
|
logger.Info().
|
|
Str("reviewer", reviewer.Login).
|
|
Msg("found reviewer in RequestedReviewer singular field")
|
|
} else if len(event.PullRequest.Assignees) > 0 {
|
|
// Approach 3: fall back to PR assignees
|
|
reviewer = &event.PullRequest.Assignees[0]
|
|
logger.Info().
|
|
Str("reviewer", reviewer.Login).
|
|
Msg("falling back to PR assignee as reviewer")
|
|
} else {
|
|
logger.Warn().Msg("no reviewer found in event payload")
|
|
return fmt.Errorf("no reviewer found for review_requested event (PR #%d)", event.Number)
|
|
}
|
|
|
|
// Resolve the reviewer's Slack identity
|
|
resolved, err := p.resolver.Resolve(ctx, User{
|
|
GiteaUsername: reviewer.Login,
|
|
GiteaID: reviewer.ID,
|
|
Email: reviewer.Email,
|
|
FullName: reviewer.FullName,
|
|
})
|
|
if err != nil {
|
|
logger.Error().
|
|
Err(err).
|
|
Str("reviewer", reviewer.Login).
|
|
Msg("failed to resolve reviewer identity")
|
|
return fmt.Errorf("resolving reviewer %s: %w", reviewer.Login, err)
|
|
}
|
|
|
|
if resolved.SlackID == "" {
|
|
logger.Warn().
|
|
Str("reviewer", reviewer.Login).
|
|
Msg("reviewer has no Slack ID, skipping DM")
|
|
return nil
|
|
}
|
|
|
|
// Send Slack DM
|
|
dmText := fmt.Sprintf(
|
|
":bell: *Review Requested*\n*Repository:* %s\n*Pull Request:* #%d - %s\n*Requested by:* %s\n\n<%s|View on Gitea>",
|
|
event.Repository.FullName,
|
|
event.Number,
|
|
event.PullRequest.Title,
|
|
event.Sender.Login,
|
|
event.PullRequest.HTMLURL,
|
|
)
|
|
|
|
if err := p.slack.SendDM(ctx, resolved.SlackID, dmText); err != nil {
|
|
logger.Error().
|
|
Err(err).
|
|
Str("slack_id", resolved.SlackID).
|
|
Msg("failed to send Slack DM")
|
|
return fmt.Errorf("sending Slack DM to %s: %w", resolved.SlackID, err)
|
|
}
|
|
|
|
logger.Info().
|
|
Str("reviewer", reviewer.Login).
|
|
Str("slack_id", resolved.SlackID).
|
|
Msg("successfully sent review_requested notification")
|
|
return nil
|
|
}
|
|
|
|
// handleOpened processes pull_request opened events
|
|
func (p *Processor) handleOpened(ctx context.Context, event *webhook.PullRequestEvent, logger zerolog.Logger) error {
|
|
logger.Info().Msg("processing PR opened event")
|
|
|
|
for _, assignee := range event.PullRequest.Assignees {
|
|
resolved, err := p.resolver.Resolve(ctx, User{
|
|
GiteaUsername: assignee.Login,
|
|
GiteaID: assignee.ID,
|
|
Email: assignee.Email,
|
|
FullName: assignee.FullName,
|
|
})
|
|
if err != nil {
|
|
logger.Error().Err(err).Str("assignee", assignee.Login).Msg("failed to resolve assignee")
|
|
continue
|
|
}
|
|
if resolved.SlackID == "" {
|
|
continue
|
|
}
|
|
text := fmt.Sprintf(
|
|
":new: *PR Opened*\n*Repository:* %s\n*PR:* #%d - %s\n*By:* %s\n\n<%s|View on Gitea>",
|
|
event.Repository.FullName,
|
|
event.Number,
|
|
event.PullRequest.Title,
|
|
event.Sender.Login,
|
|
event.PullRequest.HTMLURL,
|
|
)
|
|
if err := p.slack.SendDM(ctx, resolved.SlackID, text); err != nil {
|
|
logger.Error().Err(err).Str("slack_id", resolved.SlackID).Msg("failed to send DM")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// handleClosed processes pull_request closed/merged events
|
|
func (p *Processor) handleClosed(ctx context.Context, event *webhook.PullRequestEvent, logger zerolog.Logger) error {
|
|
if event.PullRequest.Merged {
|
|
logger.Info().Msg("processing PR merged event")
|
|
} else {
|
|
logger.Info().Msg("processing PR closed (without merge) event")
|
|
}
|
|
|
|
verb := "Closed"
|
|
if event.PullRequest.Merged {
|
|
verb = "Merged"
|
|
}
|
|
|
|
for _, assignee := range event.PullRequest.Assignees {
|
|
resolved, err := p.resolver.Resolve(ctx, User{
|
|
GiteaUsername: assignee.Login,
|
|
GiteaID: assignee.ID,
|
|
Email: assignee.Email,
|
|
FullName: assignee.FullName,
|
|
})
|
|
if err != nil {
|
|
logger.Error().Err(err).Str("assignee", assignee.Login).Msg("failed to resolve assignee")
|
|
continue
|
|
}
|
|
if resolved.SlackID == "" {
|
|
continue
|
|
}
|
|
var emoji string
|
|
if event.PullRequest.Merged {
|
|
emoji = ":white_check_mark:"
|
|
} else {
|
|
emoji = ":x:"
|
|
}
|
|
text := fmt.Sprintf(
|
|
"%s *PR %s*\n*Repository:* %s\n*PR:* #%d - %s\n\n<%s|View on Gitea>",
|
|
emoji,
|
|
verb,
|
|
event.Repository.FullName,
|
|
event.Number,
|
|
event.PullRequest.Title,
|
|
event.PullRequest.HTMLURL,
|
|
)
|
|
if err := p.slack.SendDM(ctx, resolved.SlackID, text); err != nil {
|
|
logger.Error().Err(err).Str("slack_id", resolved.SlackID).Msg("failed to send DM")
|
|
}
|
|
}
|
|
return nil
|
|
}
|