(apiOrigin, hash string)
| 643 | } |
| 644 | |
| 645 | func fetchReviewComments(apiOrigin, hash string) ([]feedbackRecord, error) { |
| 646 | origin := strings.TrimRight(strings.TrimSpace(apiOrigin), "/") |
| 647 | if origin == "" { |
| 648 | return nil, errors.New("api origin is required") |
| 649 | } |
| 650 | if hash == "" { |
| 651 | return nil, errors.New("hash is required") |
| 652 | } |
| 653 | |
| 654 | endpoint := origin + "/api/hash/" + url.PathEscape(hash) + "/feedback" |
| 655 | req, err := http.NewRequest(http.MethodGet, endpoint, nil) |
| 656 | if err != nil { |
| 657 | return nil, fmt.Errorf("create feedback request: %w", err) |
| 658 | } |
| 659 | req.Header.Set("accept", "application/json") |
| 660 | |
| 661 | client := &http.Client{Timeout: 20 * time.Second} |
| 662 | res, err := client.Do(req) |
| 663 | if err != nil { |
| 664 | return nil, fmt.Errorf("failed to reach %s: %w", endpoint, err) |
| 665 | } |
| 666 | defer res.Body.Close() |
| 667 | |
| 668 | body, err := io.ReadAll(io.LimitReader(res.Body, 2<<20)) |
| 669 | if err != nil { |
| 670 | return nil, fmt.Errorf("read feedback response: %w", err) |
| 671 | } |
| 672 | if res.StatusCode < 200 || res.StatusCode >= 300 { |
| 673 | return nil, fmt.Errorf("feedback request failed (%d): %s", res.StatusCode, strings.TrimSpace(string(body))) |
| 674 | } |
| 675 | |
| 676 | var payload feedbackResponse |
| 677 | if err := json.Unmarshal(body, &payload); err != nil { |
| 678 | return nil, fmt.Errorf("decode feedback response: %w", err) |
| 679 | } |
| 680 | |
| 681 | comments := make([]feedbackRecord, 0, len(payload.Feedback)) |
| 682 | for _, item := range payload.Feedback { |
| 683 | if strings.TrimSpace(item.Message) == "" { |
| 684 | continue |
| 685 | } |
| 686 | comments = append(comments, item) |
| 687 | } |
| 688 | sort.Slice(comments, func(i, j int) bool { |
| 689 | if comments[i].BlockID != comments[j].BlockID { |
| 690 | return comments[i].BlockID < comments[j].BlockID |
| 691 | } |
| 692 | if comments[i].CreatedAt != comments[j].CreatedAt { |
| 693 | return comments[i].CreatedAt < comments[j].CreatedAt |
| 694 | } |
| 695 | return comments[i].ID < comments[j].ID |
| 696 | }) |
| 697 | |
| 698 | return comments, nil |
| 699 | } |
| 700 | |
| 701 | func renderFeedbackMarkdown(hash string, comments []feedbackRecord) string { |
| 702 | var b strings.Builder |
no test coverage detected