driesvints / sponsors

A package for PHP to interact with GitHub Sponsors.
MIT License
104 stars 6 forks source link

Sync sponsorships with GitHub webhooks #6

Open driesvints opened 3 years ago

driesvints commented 3 years ago

It would be cool if we could sync incoming GitHub webhooks into a database table so sponsorships can be checked against a persistent storage instead of performing GraphQL API calls.

We could leverage Spatie's https://github.com/spatie/laravel-github-webhooks package for this maybe.

This is atm a pretty vague idea as I'm not sure how practically this would be. Would we only sync sponsorships for the authed user on the client? Or more?

Also see https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#sponsorship

claudiodekker commented 3 years ago

Posting for relevance / use-for-parts (copied from my WIP inertiajs implementation):

public function organizationIds(): array
{
    $query = <<<'EOF'
        query {
          viewer {
            organizations(first: 100) {
              nodes {
                databaseId
              }
            }
          }
        }
    EOF;

    $response = $this->execute($query);

    return Collection::make(Arr::get($response, 'viewer.organizations.nodes', []))
        ->pluck('databaseId')
        ->all();
}

I personally found databaseId fields to be more (theoretically) reliable than a login, and use that to check against existing sponsors exactly like this ticket describes. Here's the fake test response:

Http::fake([
    'https://api.github.com/graphql' => Http::response([
        'data' => [
            'viewer' => [
                'organizations' => [
                    "nodes" => [
                        ["databaseId" => 958072],
                        ["databaseId" => 39676034],
                        ["databaseId" => 47703742],
                    ]
                ],
            ],
        ],
    ]),
]);

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class GithubWebhookRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        $secret = config('services.github.webhook_secret');
        if (is_null($secret)) {
            return true;
        }

        $signature = $this->headers->get('X-Hub-Signature-256', '');
        $hash = 'sha256='.hash_hmac('sha256', (string) $this->getContent(), $secret);

        return hash_equals($hash, $signature);
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            //
        ];
    }
}
/** @test */
public function it_allows_the_request_when_a_webhook_secret_is_set_and_the_correct_hash_header_is_used(): void
{
    config(['services.github.webhook_secret' => 'secret']);
    $payload = $this->getSponsorsPayload('created');

    $headers = ['X-Hub-Signature-256' => 'sha256='.hash_hmac('sha256', json_encode($payload), 'secret')];
    $this->postJson('/api/github/webhooks/sponsorship', $payload, $headers)
        ->assertStatus(200);

    $this->assertTrue(GithubSponsor::where('github_api_login', 'monalisa')->exists());
}
claudiodekker commented 3 years ago

I also have a very WIP but functional Github Webhook implementation already.

Let me know if you want it by the time you intend to work on this feature, as by that point I imagine I can provide you either directly with a link the the InertiaJS repo where it's open-sourced, or provide you with an up-to-date sample instead of posting an outdated copy here & forgetting to update it down the line.