feat. rebuild service in Go
Rebuild the Gitea Codex review bot from the product contract with a Go HTTP service, durable SQL queue, typed Gitea client, isolated runner, and deployment updates. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea-codex-bot/internal/domain"
|
||||
)
|
||||
|
||||
func VerifySignature(body []byte, secret, supplied string) bool {
|
||||
supplied = strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(supplied), "sha256="))
|
||||
if supplied == "" {
|
||||
return false
|
||||
}
|
||||
mac := hmac.New(sha256.New, []byte(secret))
|
||||
_, _ = mac.Write(body)
|
||||
expected := hex.EncodeToString(mac.Sum(nil))
|
||||
return hmac.Equal([]byte(strings.ToLower(expected)), []byte(strings.ToLower(supplied)))
|
||||
}
|
||||
|
||||
func Digest(body []byte) string { sum := sha256.Sum256(body); return hex.EncodeToString(sum[:]) }
|
||||
|
||||
func ParseEvent(eventName, delivery string, body []byte) (domain.WebhookEvent, error) {
|
||||
if eventName != "issue_comment" && eventName != "pull_request_comment" {
|
||||
return domain.WebhookEvent{}, errors.New("event ignored")
|
||||
}
|
||||
var raw map[string]any
|
||||
if err := json.Unmarshal(body, &raw); err != nil {
|
||||
return domain.WebhookEvent{}, errors.New("invalid JSON payload")
|
||||
}
|
||||
repo := nestedString(raw, "repository", "full_name")
|
||||
commentID := nestedInt64(raw, "comment", "id")
|
||||
sender := nestedString(raw, "sender", "username")
|
||||
commentBody := nestedString(raw, "comment", "body")
|
||||
if repo == "" || commentID <= 0 {
|
||||
return domain.WebhookEvent{}, errors.New("not a pull request comment")
|
||||
}
|
||||
prNumber, headSHA := 0, ""
|
||||
if eventName == "issue_comment" {
|
||||
if _, ok := raw["pull_request"]; !ok || raw["pull_request"] == nil {
|
||||
return domain.WebhookEvent{}, errors.New("not a pull request comment")
|
||||
}
|
||||
issue, ok := raw["issue"].(map[string]any)
|
||||
if !ok || !truthy(issue["pull_request"]) {
|
||||
return domain.WebhookEvent{}, errors.New("not a pull request comment")
|
||||
}
|
||||
prNumber = number(issue["number"])
|
||||
headSHA = nestedString(raw, "pull_request", "head", "sha")
|
||||
} else {
|
||||
pr, ok := raw["pull_request"].(map[string]any)
|
||||
if !ok || pr == nil {
|
||||
return domain.WebhookEvent{}, errors.New("not a pull request comment")
|
||||
}
|
||||
prNumber = number(pr["number"])
|
||||
headSHA = nestedString(raw, "pull_request", "head", "sha")
|
||||
}
|
||||
if prNumber <= 0 {
|
||||
return domain.WebhookEvent{}, errors.New("not a pull request comment")
|
||||
}
|
||||
if headSHA == "" {
|
||||
headSHA = "unknown"
|
||||
}
|
||||
return domain.WebhookEvent{EventName: eventName, DeliveryID: delivery, Repo: repo, PRNumber: prNumber, HeadSHA: headSHA, CommentID: commentID, CommentBody: strings.TrimSpace(commentBody), Sender: sender, PayloadSHA256: Digest(body)}, nil
|
||||
}
|
||||
|
||||
func nestedString(raw map[string]any, path ...string) string {
|
||||
var current any = raw
|
||||
for _, key := range path {
|
||||
obj, ok := current.(map[string]any)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
current = obj[key]
|
||||
}
|
||||
if value, ok := current.(string); ok {
|
||||
return value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func nestedInt64(raw map[string]any, path ...string) int64 {
|
||||
var current any = raw
|
||||
for _, key := range path {
|
||||
obj, ok := current.(map[string]any)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
current = obj[key]
|
||||
}
|
||||
return int64(number(current))
|
||||
}
|
||||
func number(value any) int {
|
||||
switch v := value.(type) {
|
||||
case float64:
|
||||
return int(v)
|
||||
case json.Number:
|
||||
n, _ := strconv.Atoi(string(v))
|
||||
return n
|
||||
case int:
|
||||
return v
|
||||
case int64:
|
||||
return int(v)
|
||||
case string:
|
||||
n, _ := strconv.Atoi(v)
|
||||
return n
|
||||
}
|
||||
return 0
|
||||
}
|
||||
func truthy(value any) bool {
|
||||
switch v := value.(type) {
|
||||
case bool:
|
||||
return v
|
||||
case map[string]any:
|
||||
return len(v) > 0
|
||||
case string:
|
||||
return strings.TrimSpace(v) != ""
|
||||
default:
|
||||
return value != nil
|
||||
}
|
||||
}
|
||||
|
||||
var _ = fmt.Sprintf
|
||||
Reference in New Issue
Block a user