discourse/plugins/discourse-github/app/controllers/discourse_github/webhooks_controller.rb
Régis Hanol f3680621bc
FEATURE: GitHub PR live status icon (#36313)
I wanted a better way to show a live/up-to-date status of a PR than what
the https://github.com/discourse/github-status-theme provided.

First, I wanted it to work on any PR, including ones in private
repositories.

Second, I wanted to include the approved status, which requires a 2nd
call to GitHub's API.

Third, I wanted something that was less "intrusive" than the
"shield-like" status, so I opted out to use the icons & color that
GitHub uses (but maybe that's a bad idea).

After a first PoC that was a dump proxy to handle authentication for private repositories, the idea to use org webhooks was raised and turned out to be much lighter and easier than figuring out a proper caching strategy.

So this adds support for receiving `pull_request` and `pull_request_review` webhooks events to schedule a background job that will rebake posts and chat messages that have a link to the mentioned PR.

The GitHub PR onebox has been updated to query and parse the review state so we can have a better icon.

This also adds the `Chat::MessageLink` model (that is the #chat version of `TopicLink`) to help better keep track of links posted in chat.

**BEFORE**

<img width="1525" height="1356" alt="BEFORE"
src="https://github.com/user-attachments/assets/02f66137-9f4f-4a91-b321-73816161e204"
/>


**AFTER**

<img width="1525" height="1356" alt="AFTER"
src="https://github.com/user-attachments/assets/2164b7ef-8cd0-4cec-aea8-936a76a94fb2"
/>


Internal ref - t/17138
2025-12-04 21:42:44 +01:00

41 lines
1.1 KiB
Ruby

# frozen_string_literal: true
module DiscourseGithub
class WebhooksController < ::ApplicationController
requires_plugin "discourse-github"
skip_before_action :check_xhr
skip_before_action :verify_authenticity_token
skip_before_action :redirect_to_login_if_required
def github
return head :forbidden unless verify_signature
event = request.headers["X-GitHub-Event"]
return head :ok if %w[pull_request pull_request_review].exclude?(event)
pr_url = params.dig(:pull_request, :html_url)
return head :ok if pr_url.blank?
Jobs.enqueue(:rebake_github_pr_posts, pr_url: pr_url)
head :ok
end
private
def verify_signature
secret = SiteSetting.github_webhook_secret
return false if secret.blank?
signature = request.headers["X-Hub-Signature-256"]
return false if signature.blank?
request.body.rewind
body = request.body.read
expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", secret, body)
ActiveSupport::SecurityUtils.secure_compare(expected, signature)
end
end
end