Skip to content

Commit 2e378e7

Browse files
committed
initial commit
1 parent b8664a5 commit 2e378e7

File tree

9 files changed

+347
-1
lines changed

9 files changed

+347
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/vendor/
2+
composer.lock
3+
phpunit.xml

README.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,39 @@
1-
# laravel-github-sponsors
1+
# Laravel GitHub Sponsors
2+
3+
Retrieve the GitHub Sponsors of a given user/organization.
4+
5+
## Installation
6+
7+
```bash
8+
composer require astrotomic/laravel-github-sponsors
9+
```
10+
11+
## Configuration
12+
13+
Set `services.github.sponsors_token` config value or override service binding in your own service provider.
14+
The used PAT needs at least `read:org` permissions to retrieve sponsoring organizations.
15+
16+
```php
17+
use Astrotomic\GithubSponsors\GithubSponsors;
18+
use Illuminate\Contracts\Container\Container;
19+
20+
$this->app->bind(GithubSponsors::class, function(Container $app): GithubSponsors {
21+
return new GithubSponsors(
22+
$app->make('config')->get('your.own.config.key')
23+
);
24+
});
25+
```
26+
27+
## Usage
28+
29+
```php
30+
use Astrotomic\GithubSponsors\Facades\GithubSponsors;
31+
32+
GithubSponsors::fromViewer()->all(); // all sponsors for current authenticated user
33+
GithubSponsors::fromUser('Gummibeer')->all(); // all sponsors for given user
34+
GithubSponsors::fromOrganization('Astrotomic')->all(); // all sponsors for given organization
35+
36+
GithubSponsors::fromViewer()->cursor(); // lazy collection - using less memory
37+
38+
GithubSponsors::fromViewer()->select('login', 'name', 'avatarUrl')->all(); // select specific attributes
39+
```

composer.json

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"name": "astrotomic/laravel-github-sponsors",
3+
"description": "",
4+
"license": "MIT",
5+
"authors": [
6+
{
7+
"name": "Tom Witkowski",
8+
"email": "gummibeer@astrotomic.info",
9+
"homepage": "https://astrotomic.info",
10+
"role": "Developer"
11+
}
12+
],
13+
"require": {
14+
"php": "^7.4 || ^8.0",
15+
"ext-json": "*",
16+
"astrotomic/graphql-query-builder": "^0.1.0",
17+
"guzzlehttp/guzzle": "^6.5.5 || ^7.0.1",
18+
"illuminate/http": "^8.0",
19+
"illuminate/support": "^8.0"
20+
},
21+
"require-dev": {
22+
"orchestra/testbench": "^5.0 || ^6.0",
23+
"pestphp/pest": "^v1.15.0",
24+
"pestphp/pest-plugin-laravel": "^1.1"
25+
},
26+
"config": {
27+
"sort-packages": true
28+
},
29+
"extra": {
30+
"composer-normalize": {
31+
"indent-size": 4,
32+
"indent-style": "space"
33+
},
34+
"laravel": {
35+
"providers": [
36+
"Astrotomic\\GithubSponsors\\GithubSponsorsServiceProvider"
37+
]
38+
}
39+
},
40+
"autoload": {
41+
"psr-4": {
42+
"Astrotomic\\GithubSponsors\\": "src"
43+
}
44+
},
45+
"autoload-dev": {
46+
"psr-4": {
47+
"Tests\\": "tests/"
48+
}
49+
},
50+
"minimum-stability": "dev",
51+
"prefer-stable": true
52+
}

phpunit.xml.dist

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
4+
bootstrap="vendor/autoload.php"
5+
colors="true"
6+
>
7+
<testsuites>
8+
<testsuite name="Test Suite">
9+
<directory suffix="Test.php">./tests</directory>
10+
</testsuite>
11+
</testsuites>
12+
<coverage processUncoveredFiles="true">
13+
<include>
14+
<directory suffix=".php">./src</directory>
15+
</include>
16+
</coverage>
17+
</phpunit>

src/Facades/GithubSponsors.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Astrotomic\GithubSponsors\Facades;
4+
5+
use Illuminate\Support\Facades\Facade;
6+
7+
/**
8+
* @method static \Astrotomic\GithubSponsors\GithubSponsors fromViewer()
9+
* @method static \Astrotomic\GithubSponsors\GithubSponsors fromUser(string $login)
10+
* @method static \Astrotomic\GithubSponsors\GithubSponsors fromOrganization(string $login)
11+
*
12+
* @see \Astrotomic\GithubSponsors\GithubSponsors
13+
*/
14+
class GithubSponsors extends Facade
15+
{
16+
protected static function getFacadeAccessor(): string
17+
{
18+
return \Astrotomic\GithubSponsors\GithubSponsors::class;
19+
}
20+
}

src/GithubSponsors.php

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
3+
namespace Astrotomic\GithubSponsors;
4+
5+
use Astrotomic\GraphqlQueryBuilder\Graph;
6+
use Astrotomic\GraphqlQueryBuilder\Query;
7+
use Exception;
8+
use Generator;
9+
use Illuminate\Support\Arr;
10+
use Illuminate\Support\Collection;
11+
use Illuminate\Support\Facades\Http;
12+
use Illuminate\Support\Fluent;
13+
use Illuminate\Support\LazyCollection;
14+
15+
class GithubSponsors
16+
{
17+
protected string $fromName;
18+
protected array $fromArguments = [];
19+
protected array $select = [
20+
'login'
21+
];
22+
protected string $token;
23+
24+
public function __construct(string $token)
25+
{
26+
$this->token = $token;
27+
}
28+
29+
public function fromViewer(): self
30+
{
31+
$this->fromName = 'viewer';
32+
$this->fromArguments = [];
33+
34+
return $this;
35+
}
36+
37+
public function fromUser(string $login): self
38+
{
39+
$this->fromName = 'user';
40+
$this->fromArguments = [
41+
'login' => $login,
42+
];
43+
44+
return $this;
45+
}
46+
47+
public function fromOrganization(string $login): self
48+
{
49+
$this->fromName = 'organization';
50+
$this->fromArguments = [
51+
'login' => $login,
52+
];
53+
54+
return $this;
55+
}
56+
57+
public function select(string ...$fields): self
58+
{
59+
$this->select = $fields;
60+
61+
return $this;
62+
}
63+
64+
public function all(): Collection
65+
{
66+
return $this->cursor()->collect();
67+
}
68+
69+
public function cursor(): LazyCollection
70+
{
71+
return LazyCollection::make(function (): Generator {
72+
$cursor = null;
73+
74+
do {
75+
$data = $this->request($this->query($cursor));
76+
$cursor = $data['pageInfo']['endCursor'];
77+
78+
foreach (data_get($data, 'nodes.*.sponsorEntity') as $sponsor) {
79+
yield new Fluent($sponsor);
80+
}
81+
} while ($data['pageInfo']['hasNextPage'] ?? false);
82+
});
83+
}
84+
85+
protected function query(?string $after = null): string
86+
{
87+
return (string) Graph::query(
88+
Query::from($this->fromName)
89+
->with($this->fromArguments)
90+
->select(
91+
Query::from('sponsorshipsAsMaintainer')
92+
->with(array_filter(['first' => 100, 'after' => $after]))
93+
->select(
94+
Query::from('pageInfo')->select('hasNextPage', 'endCursor'),
95+
Query::from('nodes')->select(
96+
Query::from('sponsorEntity')->select(
97+
'__typename',
98+
Query::for('User')->select(...$this->select),
99+
Query::for('Organization')->select(...$this->select),
100+
)
101+
)
102+
)
103+
)
104+
);
105+
}
106+
107+
protected function request(string $query): array
108+
{
109+
$response = Http::baseUrl('https://api.github.com')
110+
->accept('application/vnd.github.v3+json')
111+
->withUserAgent('astrotomic/laravel-github-sponsors')
112+
->withOptions(['http_errors' => true])
113+
->withToken($this->token)
114+
->post('/graphql', ['query' => $query])
115+
->json();
116+
117+
if (array_key_exists('errors', $response)) {
118+
throw new Exception(Arr::first($response['errors'])['message']);
119+
}
120+
121+
return data_get($response, "data.{$this->fromName}.sponsorshipsAsMaintainer");
122+
}
123+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Astrotomic\GithubSponsors;
4+
5+
use Illuminate\Contracts\Container\Container;
6+
use Illuminate\Support\ServiceProvider;
7+
8+
class GithubSponsorsServiceProvider extends ServiceProvider
9+
{
10+
public function register(): void
11+
{
12+
$this->app->bind(GithubSponsors::class, function(Container $app): GithubSponsors {
13+
return new GithubSponsors(
14+
$app->make('config')->get('services.github.sponsors_token')
15+
);
16+
});
17+
}
18+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
use Astrotomic\GithubSponsors\Facades\GithubSponsors;
4+
use Illuminate\Support\Collection;
5+
use Illuminate\Support\Fluent;
6+
use Illuminate\Support\LazyCollection;
7+
use Pest\Expectation;
8+
9+
it('retrieves sponsors for authenticated user')
10+
->expect(fn() => GithubSponsors::fromViewer()->all())
11+
->toBeInstanceOf(Collection::class)
12+
->not->toBeEmpty()
13+
->each(static function(Expectation $value): void {
14+
$value
15+
->toBeInstanceOf(Fluent::class)
16+
->__typename->toBeString()->toBeIn(['User', 'Organization'])
17+
->login->toBeString();
18+
});
19+
20+
it('retrieves sponsors for given user')
21+
->expect(fn() => GithubSponsors::fromUser('Gummibeer')->all())
22+
->toBeInstanceOf(Collection::class)
23+
->not->toBeEmpty()
24+
->each(static function(Expectation $value): void {
25+
$value
26+
->toBeInstanceOf(Fluent::class)
27+
->__typename->toBeString()->toBeIn(['User', 'Organization'])
28+
->login->toBeString();
29+
});
30+
31+
it('retrieves sponsors for given organization')
32+
->expect(fn() => GithubSponsors::fromOrganization('larabelles')->all())
33+
->toBeInstanceOf(Collection::class)
34+
->not->toBeEmpty()
35+
->each(static function(Expectation $value): void {
36+
$value
37+
->toBeInstanceOf(Fluent::class)
38+
->__typename->toBeString()->toBeIn(['User', 'Organization'])
39+
->login->toBeString();
40+
});
41+
42+
it('retrieves sponsors with custom fields')
43+
->expect(fn() => GithubSponsors::fromViewer()->select('login', 'databaseId')->all())
44+
->toBeInstanceOf(Collection::class)
45+
->not->toBeEmpty()
46+
->each(static function(Expectation $value): void {
47+
$value
48+
->toBeInstanceOf(Fluent::class)
49+
->__typename->toBeString()->toBeIn(['User', 'Organization'])
50+
->login->toBeString()
51+
->databaseId->toBeInt()->toBeGreaterThan(0);
52+
});
53+
54+
it('retrieves sponsors lazyly')
55+
->expect(fn() => GithubSponsors::fromViewer()->cursor())
56+
->toBeInstanceOf(LazyCollection::class)
57+
->not->toBeEmpty()
58+
->each(static function(Expectation $value): void {
59+
$value
60+
->toBeInstanceOf(Fluent::class)
61+
->__typename->toBeString()->toBeIn(['User', 'Organization'])
62+
->login->toBeString();
63+
});

tests/Pest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
use Astrotomic\GithubSponsors\GithubSponsorsServiceProvider;
4+
use Orchestra\Testbench\TestCase;
5+
6+
uses(TestCase::class)->in('Feature');
7+
8+
uses()->beforeEach(function(): void {
9+
$this->app->register(GithubSponsorsServiceProvider::class);
10+
11+
config()->set('services.github.sponsors_token', env('GITHUB_SPONSORS_TOKEN'));
12+
})->in('Feature');

0 commit comments

Comments
 (0)