Skip to content

Add YouTube Data API client #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Mar 5, 2021
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0169e13
Remove facade reference - won't work because of Google services objec…
kylebarney Feb 27, 2021
a192737
Refactor to support one or more sets of Google client secrets
kylebarney Feb 27, 2021
d21bbcc
Make GMB access token name more specific
kylebarney Feb 27, 2021
c7a2cf6
Add default option for access token slug
kylebarney Feb 27, 2021
e7a1335
Typo fix
kylebarney Feb 27, 2021
947e69b
Add YouTube config
kylebarney Feb 27, 2021
7513578
F/R fix
kylebarney Feb 27, 2021
565e05a
Add new .env options
kylebarney Feb 27, 2021
709876b
Document config arrangement
kylebarney Feb 27, 2021
98af322
Add scopes config setting and add default for configs that run 'explo…
kylebarney Feb 27, 2021
5cfd8e9
Add default GMB scope for backwards compatibility
kylebarney Feb 27, 2021
b41ea88
Add YouTube service binding and abstract out client configuration
kylebarney Feb 27, 2021
841db14
Fix styling
kylebarney Feb 27, 2021
d9112ac
Add test case for GMB client creation
kylebarney Feb 27, 2021
0791b64
Merge branch 'kyle-feature-add-additional-Google-services' of github.…
kylebarney Feb 27, 2021
8ac76f6
Fix styling
kylebarney Feb 27, 2021
ac73295
Add mock json token for testing Google client initialization
kylebarney Mar 5, 2021
fff3292
Add test for YouTube service instantiation
kylebarney Mar 5, 2021
d6c0370
Add gitignore for .env.test
kylebarney Mar 5, 2021
c3c62bb
Merge branch main
kylebarney Mar 5, 2021
db6d351
Get tests back to passing after merge
kylebarney Mar 5, 2021
aa9741f
Styling update merge
kylebarney Mar 5, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
GOOGLE_CLIENT_ID=
GOOGLE_PROJECT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URIS=
GOOGLE_JAVASCRIPT_ORIGINS=
GOOGLE_SCOPES=

GOOGLE_MYBUSINESS_CLIENT_ID=
GOOGLE_MYBUSINESS_PROJECT_ID=
GOOGLE_MYBUSINESS_CLIENT_SECRET=
GOOGLE_MYBUSINESS_REDIRECT_URIS=
GOOGLE_MYBUSINESS_JAVASCRIPT_ORIGINS=
GOOGLE_MYBUSINESS_SCOPES=

ACCESS_TOKEN_SLUG=gmb-token
ACCESS_TOKEN_VALUE=
GOOGLE_MYBUSINESS_ACCESS_TOKEN_SLUG=gmb-token
GOOGLE_MYBUSINESS_ACCESS_TOKEN_VALUE=
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -11,3 +11,4 @@ phpunit.xml
psalm.xml
vendor
.env
.env.test
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -22,18 +22,22 @@ php artisan vendor:publish --provider="Tipoff\GoogleApi\GoogleApiServiceProvider
Add the following variables to your `.env` file and set them based on the contents of the
`client_secret.json` file you obtained from Google.
```
GOOGLE_MYBUSINESS_CLIENT_ID=
GOOGLE_MYBUSINESS_PROJECT_ID=
GOOGLE_MYBUSINESS_CLIENT_SECRET=
GOOGLE_MYBUSINESS_REDIRECT_URIS=
GOOGLE_MYBUSINESS_JAVASCRIPT_ORIGINS=
GOOGLE_CLIENT_ID=
GOOGLE_PROJECT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URIS=
GOOGLE_JAVASCRIPT_ORIGINS=
```

You can use the `|` character to separate multiple strings in the `GOOGLE_MYBUSINESS_REDIRECT_URIS` and `GOOGLE_MYBUSINESS_JAVASCRIPT_ORIGINS` settings.
You can use the `|` character to separate multiple strings in the `GOOGLE_REDIRECT_URIS` and `GOOGLE_JAVASCRIPT_ORIGINS` settings.

Obtain an API access token for Google My Business and insert it into the `keys` table, giving it an identifying slug for the next step. (Make sure that the `value` field contains your token formatted as valid JSON.)
Obtain an API access token from Google and insert it into the `keys` table, giving it an identifying slug for the next step. (Make sure that the `value` field contains your token formatted as valid JSON.)

Set the value of `ACCESS_TOKEN_SLUG` in your `.env` file to the name of the slug in your `keys` table entry.
Set the value of `GOOGLE_ACCESS_TOKEN_SLUG` in your `.env` file to the name of the slug in your `keys` table entry.

**Note:** If you need to customize the client secret fields or access token value on a service-by-service basis, you may do so by setting values in your `.env` file for the service-specific values found in `config/google-api.php`. (For example, `YOUTUBE_CLIENT_ID`.)

If you do not set service-specific values, it will default to the options set above for each service.

## Usage

5 changes: 1 addition & 4 deletions composer.json
Original file line number Diff line number Diff line change
@@ -51,10 +51,7 @@
"laravel": {
"providers": [
"Tipoff\\GoogleApi\\GoogleApiServiceProvider"
],
"aliases": {
"GoogleMyBusiness": "Tipoff\\GoogleAPI\\Facades\\GoogleMyBusinessFacade"
}
]
}
},
"repositories": [
40 changes: 31 additions & 9 deletions config/google-api.php
Original file line number Diff line number Diff line change
@@ -4,16 +4,38 @@
'my-business' => [
'client-secret' => [
"web" => [
"client_id" => env('GOOGLE_MYBUSINESS_CLIENT_ID'),
"project_id" => env('GOOGLE_MYBUSINESS_PROJECT_ID'),
"auth_uri" => env('GOOGLE_MYBUSINESS_AUTH_URI', 'https://accounts.google.com/o/oauth2/auth'),
"token_uri" => env('GOOGLE_MYBUSINESS_TOKEN_URI', 'https://oauth2.googleapis.com/token'),
"auth_provider_x509_cert_url" => env('GOOGLE_MYBUSINESS_CERT_URL', 'https://www.googleapis.com/oauth2/v1/certs'),
"client_secret" => env('GOOGLE_MYBUSINESS_CLIENT_SECRET'),
"redirect_uris" => explode('|', env('GOOGLE_MYBUSINESS_REDIRECT_URIS')),
"javascript_origins" => explode('|', env('GOOGLE_MYBUSINESS_JAVASCRIPT_ORIGINS')),
"client_id" => env('GOOGLE_MYBUSINESS_CLIENT_ID') ?? env('GOOGLE_CLIENT_ID'),
"project_id" => env('GOOGLE_MYBUSINESS_PROJECT_ID') ?? env('GOOGLE_PROJECT_ID'),
"auth_uri" => env('GOOGLE_MYBUSINESS_AUTH_URI', env('GOOGLE_AUTH_URI', 'https://accounts.google.com/o/oauth2/auth')),
"token_uri" => env('GOOGLE_MYBUSINESS_TOKEN_URI', env('GOOGLE_TOKEN_URI', 'https://oauth2.googleapis.com/token')),
"auth_provider_x509_cert_url" => env('GOOGLE_MYBUSINESS_CERT_URL', env('GOOGLE_CERT_URL', 'https://www.googleapis.com/oauth2/v1/certs')),
"client_secret" => env('GOOGLE_MYBUSINESS_CLIENT_SECRET') ?? env('GOOGLE_CLIENT_SECRET'),
"redirect_uris" => explode('|', env('GOOGLE_MYBUSINESS_REDIRECT_URIS') ?? env('GOOGLE_REDIRECT_URIS', '')),
"javascript_origins" => explode('|', env('GOOGLE_MYBUSINESS_JAVASCRIPT_ORIGINS') ?? env('GOOGLE_JAVASCRIPT_ORIGINS', '')),
"scopes" => explode('|', env('GOOGLE_MYBUSINESS_SCOPES') ?? env('GOOGLE_SCOPES', 'https://www.googleapis.com/auth/business.manage')),
],
],
'access-token-slug' => env('ACCESS_TOKEN_SLUG', 'gmb-token'),
'access-token-slug' => env('GOOGLE_MYBUSINESS_ACCESS_TOKEN_SLUG', env('GOOGLE_ACCESS_TOKEN_SLUG', 'gmb-token')),
],

'youtube' => [
'client-secret' => [
"web" => [
"client_id" => env('YOUTUBE_CLIENT_ID') ?? env('GOOGLE_CLIENT_ID'),
"project_id" => env('YOUTUBE_PROJECT_ID') ?? env('GOOGLE_PROJECT_ID'),
"auth_uri" => env('YOUTUBE_AUTH_URI', env('GOOGLE_AUTH_URI', 'https://accounts.google.com/o/oauth2/auth')),
"token_uri" => env('YOUTUBE_TOKEN_URI', env('GOOGLE_TOKEN_URI', 'https://oauth2.googleapis.com/token')),
"auth_provider_x509_cert_url" => env('YOUTUBE_CERT_URL', env('GOOGLE_CERT_URL', 'https://www.googleapis.com/oauth2/v1/certs')),
"client_secret" => env('YOUTUBE_CLIENT_SECRET') ?? env('GOOGLE_CLIENT_SECRET'),
"redirect_uris" => explode('|', env('YOUTUBE_REDIRECT_URIS') ?? env('GOOGLE_REDIRECT_URIS', '')),
"javascript_origins" => explode('|', env('YOUTUBE_JAVASCRIPT_ORIGINS') ?? env('GOOGLE_JAVASCRIPT_ORIGINS', '')),
"scopes" => explode('|', env('YOUTUBE_SCOPES') ?? env('GOOGLE_SCOPES', '')),
],
],
'access-token-slug' => env('YOUTUBE_ACCESS_TOKEN_SLUG', env('GOOGLE_ACCESS_TOKEN_SLUG', 'youtube-token')),
],

'test' => [
'mock-json-token' => '{"access_token":"mock-access-token","expires_in":3599,"scope":"https:\/\/www.googleapis.com\/auth\/business.manage","token_type":"Bearer","created":'.time().',"refresh_token":"mock-refresh-token"}',
]
];
16 changes: 0 additions & 16 deletions src/Facades/GoogleMyBusinessFacade.php

This file was deleted.

49 changes: 33 additions & 16 deletions src/GoogleApiServiceProvider.php
Original file line number Diff line number Diff line change
@@ -4,8 +4,10 @@

namespace Tipoff\GoogleApi;

use Exception;
use Google_Client;
use Google_Service_MyBusiness;
use Google_Service_YouTube;
use Tipoff\GoogleApi\Models\GmbAccount;
use Tipoff\GoogleApi\Models\Key;
use Tipoff\GoogleApi\Policies\GmbAccountPolicy;
@@ -39,26 +41,41 @@ public function register()
});

$this->app->bind(Google_Service_MyBusiness::class, function () {
$client = app()->make(Google_Client::class);
$client = $this->configureClient(app()->make(Google_Client::class), 'my-business');

$client->setAuthConfig(config('google-api.my-business.client-secret'));
$client->addScope(['https://www.googleapis.com/auth/business.manage']);
$client->setAccessType('offline');
return new Google_Service_MyBusiness($client);
});

$this->app->bind(Google_Service_YouTube::class, function () {
$client = $this->configureClient(app()->make(Google_Client::class), 'youtube');

return new Google_Service_YouTube($client);
});
}

$token = json_decode(Key::where('slug', config('google-api.my-business.access-token-slug'))->first()->value, true);
$client->setAccessToken($token);
protected function configureClient(Google_Client $client, string $configKey) : Google_Client
{
if (! config()->has("google-api.$configKey")) {
throw new Exception('Invalid Google service configuration specified.');
}

if ($client->isAccessTokenExpired()) {
$client->refreshToken(array_search('refresh_token', $token));
$token = $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
$client->setAuthConfig(config("google-api.$configKey.client-secret"));
$client->addScope(config("google-api.$configKey.scopes"));
$client->setAccessType('offline');

Key::updateOrCreate(
['slug' => config('google-api.my-business.access-token-slug')],
['value' => json_encode($token)]
);
}
$token = json_decode(Key::where('slug', config("google-api.$configKey.access-token-slug"))->firstOrFail()->value, true);
$client->setAccessToken($token);

return new Google_Service_MyBusiness($client);
});
if ($client->isAccessTokenExpired()) {
$client->refreshToken(array_search('refresh_token', $token));
$token = $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());

Key::updateOrCreate(
['slug' => config("google-api.$configKey.access-token-slug")],
['value' => json_encode($token)]
);
}

return $client;
}
}
16 changes: 0 additions & 16 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
@@ -7,27 +7,11 @@
use Spatie\Permission\PermissionServiceProvider;
use Tipoff\Authorization\AuthorizationServiceProvider;
use Tipoff\GoogleApi\GoogleApiServiceProvider;
use Tipoff\GoogleApi\Models\Key;
use Tipoff\Support\SupportServiceProvider;
use Tipoff\TestSupport\BaseTestCase;

class TestCase extends BaseTestCase
{
public function setUp(): void
{
parent::setUp();

if (file_exists(dirname(__DIR__) . '/.env')) {
$dotenv = \Dotenv\Dotenv::createImmutable(__DIR__.'/../');
$dotenv->load();

Key::updateOrCreate(
['slug' => config('google-api.my-business.access-token-slug')],
['value' => env('ACCESS_TOKEN_VALUE')]
);
}
}

protected function getPackageProviders($app)
{
return [
78 changes: 78 additions & 0 deletions tests/Unit/Services/GoogleServicesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

namespace Tipoff\GoogleApi\Tests\Unit\Services;

use Google_Service_MyBusiness;
use Google_Service_YouTube;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tipoff\GoogleApi\Models\Key;
use Tipoff\GoogleApi\Tests\TestCase;

class GoogleServicesTest extends TestCase
{
use RefreshDatabase;

public function setUp(): void
{
parent::setUp();

// Because of the order in which Testbench loads things, we don't
// have access to our .env.test variables when the config is initially
// set. Override the config settings here so that we can test properly.
if (file_exists(dirname(__DIR__) . '/../../.env.test')) {
$dotenv = \Dotenv\Dotenv::createImmutable(__DIR__.'/../../../', '.env.test');
$dotenv->load();

config(['google-api' => include(realpath(__DIR__.'/../../../config/google-api.php'))]);

Key::updateOrCreate(
['slug' => config('google-api.my-business.access-token-slug')],
[
'value' => env('GOOGLE_MYBUSINESS_ACCESS_TOKEN_VALUE'),
'creator_id' => randomOrCreate(app('user')),
'updater_id' => randomOrCreate(app('user')),
]
);
}
}

/** @test */
public function it_builds_the_Google_My_Business_service()
{
// If we don't have an actual key for our testing in the .env.test
// file, create a mock one here that the Google client will accept
// for creating the client.
Key::firstOrCreate(
['slug' => config('google-api.my-business.access-token-slug')],
[
'value' => config('google-api.test.mock-json-token'),
'creator_id' => randomOrCreate(app('user')),
'updater_id' => randomOrCreate(app('user')),
]
);

$service = app()->make(Google_Service_MyBusiness::class);

$this->assertInstanceOf(Google_Service_MyBusiness::class, $service);
}

/** @test */
public function it_builds_the_Google_YouTube_service()
{
// If we don't have an actual key for our testing in the .env.test
// file, create a mock one here that the Google client will accept
// for creating the client.
Key::firstOrCreate(
['slug' => config('google-api.youtube.access-token-slug')],
[
'value' => config('google-api.test.mock-json-token'),
'creator_id' => randomOrCreate(app('user')),
'updater_id' => randomOrCreate(app('user')),
]
);

$service = app()->make(Google_Service_YouTube::class);

$this->assertInstanceOf(Google_Service_YouTube::class, $service);
}
}