Google Calendar integration

Integrating Google Calendars

Episode 1
6 years ago
16 min read

You are about to learn how to allow our users to integrate all of their Google calendars and events within our application. In a series of articles we are going to see how to fetch and map Google data into beloved Laravel models and keep that data up-to-date at all times.

Whilst your application might not urgently need such integrations, we will go through several patterns and learn some clean ways to clip external data to your projects. If you haven't worked with the Google API before, you will see that it brings lots of challenges and weirdness that require us to be extra vigilant to the organization of our code which makes it a good recreational-programming exercise.

In this first article we are going to define our models, set up the Google API as a service and finally fetch, map and display calendars and events.

Some first requirements

Before diving into the code, let's take a step back and define the very first specifications of our application.

  • It isn't unlikely that users have more than one Google account. Therefore, it would be good if our application had a page to list, add and delete Google accounts. Each Google account will go through its own OAuth process and will save its own authorization token.
  • We should also import calendars and events upon creation of each Google account and map them into our own representation of those models.
  • Finally we want our users to browse through their events across all of the calendars of all of their Google accounts. For this purpose, we will need a page that sorts event by descending starting time with a little badge that indicates from which calendar this event comes from.

Looks like we've found our models.

Models

Models

User

The user model stays the same but gets a hasMany relationship to the GoogleAccount model.

class User extends Authenticatable
{
    // ...
    public function googleAccounts()
    {
        return $this->hasMany(GoogleAccount::class);
    }
}

GoogleAccount

Every model that comes from the Google API, i.e. GoogleAccount, Calendar and Event have a column google_id which keeps track of the identifier of the resource that Google understands. This will be helpful at a later stage to know if we need to create a new resource or simply update an existing one.

For the GoogleAccount model, it will be helpful to ensure a user doesn't enter twice the same account. This model also has a name (typically the email address of the account) and an authentication token which is stored as JSON.

class GoogleAccount extends Model
{
    protected $fillable = ['google_id', 'name', 'token'];
    protected $casts = ['token' => 'json'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function calendars()
    {
        return $this->hasMany(Calendar::class);
    }
}

Calendar

Aside from its name and google_id, our simplified Calendar model also has a color and a timezone.

class Calendar extends Model
{
    protected $fillable = ['google_id', 'name', 'color', 'timezone'];

    public function googleAccount()
    {
        return $this->belongsTo(GoogleAccount::class);
    }

    public function events()
    {
        return $this->hasMany(Event::class);
    }
}

Event

Finally our Event model has a name, a description and a starting and ending datetime with an allday flag letting us know if we should ignore the time of our datetime columns.

Instead of just casting our datetime columns, we provide custom accessors to incorporate the calendar's timezone into the parsed Carbon instance. We also define an accessor to retrieve the event's duration.

class Event extends Model
{
    protected $with = ['calendar'];
    protected $fillable = ['google_id', 'name', 'description', 'allday', 'started_at', 'ended_at'];

    public function calendar()
    {
        return $this->belongsTo(Calendar::class);
    }

    public function getStartedAtAttribute($start)
    {
        return $this->asDateTime($start)->setTimezone($this->calendar->timezone);
    }

    public function getEndedAtAttribute($end)
    {
        return $this->asDateTime($end)->setTimezone($this->calendar->timezone);
    }

    public function getDurationAttribute()
    {
        return $this->started_at->diffForHumans($this->ended_at, true);
    }
}

🐙 See changes on GitHub

Enabling the Google API

In order to communicate with the Google API, let's start by installing their PHP client.

composer require google/apiclient

Next, we need to get some credentials from the API and whitelist a few URIs. This is a pretty long and boring set up so I've externalised it into a gist that describes the entire process with a lot of screenshots.

Basically, we will end up with two environment variables GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET and we will whitelist the following URIs also defined as environment variables:

GOOGLE_REDIRECT_URI="${APP_URL}/google/oauth"
GOOGLE_WEBHOOK_URI="${APP_URL}/google/webhook"

Enabling Google API process

Before you do (or do not) go through this Google API set up, please note a few things:

  • Domain verification will be necessary when receiving webhook notifications from Google. Therefore it will not be used in the article but in part 3 of this series. However I recommend you to get it over and done with now.
  • I will be using valet share to obtain a live domain to share to Google. If you wish to do the same (because you want to try this locally), I highly recommend you to sign up for a free ngrok plan and link it to valet. If you use ngrok without any account, your server will expire after a few hours which will make you go through a big chunk of the set up again.
  • If you've done this before and you know what you're doing: we’re going to enable the "Google Calendar API" and the "Google+ API".

⚡️ Okay so here is the gist ⚡️

See you back in a mo.

Google as a service

Welcome back, 👋 at this point you should be able to add the following variables to your .env file:

GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI="${APP_URL}/google/oauth"
GOOGLE_WEBHOOK_URI="${APP_URL}/google/webhook"

# In my case:
APP_URL=https://b3093b51.ngrok.io

Within our config/services.php file, we configure a new google service containing everything we need to start up a Google client. It registers our environment variables but also configures some extra options.

return [
    // ...
    
    'google' => [
        // Our Google API credentials.
        'client_id' => env('GOOGLE_CLIENT_ID'),
        'client_secret' => env('GOOGLE_CLIENT_SECRET'),
        
        // The URL to redirect to after the OAuth process.
        'redirect_uri' => env('GOOGLE_REDIRECT_URI'),
        
        // The URL that listens to Google webhook notifications (Part 3).
        'webhook_uri' => env('GOOGLE_WEBHOOK_URI'),
        
        // Let the user know what we will be using from his Google account.
        'scopes' => [
            // Getting access to the user's email.
            \Google_Service_Oauth2::USERINFO_EMAIL,
            
            // Managing the user's calendars and events.
            \Google_Service_Calendar::CALENDAR,
        ],
        
        // Enables automatic token refresh.
        'approval_prompt' => 'force',
        'access_type' => 'offline',
        
        // Enables incremental scopes (useful if in the future we need access to another type of data).
        'include_granted_scopes' => true,
    ],
];

Whilst the native php client written by Google is quite powerful, we will create a new App\Services\Google class to provide an additional layer of encapsulation that we can control and that acts as our own representation of the interface between the Google API and our application.

Google service

Whenever we create a new instance of that class, we will boot up an underlying php client with all of the configurations defined earlier.

namespace App\Services;

class Google
{
    protected $client;

    function __construct()
    {
        $client = new \Google_Client();
        $client->setClientId(config('services.google.client_id'));
        $client->setClientSecret(config('services.google.client_secret'));
        $client->setRedirectUri(config('services.google.redirect_uri'));
        $client->setScopes(config('services.google.scopes'));
        $client->setApprovalPrompt(config('services.google.approval_prompt'));
        $client->setAccessType(config('services.google.access_type'));
        $client->setIncludeGrantedScopes(config('services.google.include_granted_scopes'));
        $this->client = $client;
    }
}

In order to bring all of the power of the underlying client to our Google service, we proxy all method calls to that client through the magic method __call().

class Google
{
    // ... 
    
    public function __call($method, $args)
    {
        if (! method_exists($this->client, $method)) {
            throw new \Exception("Call to undefined method '{$method}'");
        }

        return call_user_func_array([$this->client, $method], $args);
    }
}

That doesn't stop us from adding more functionalities to our Google class. For example, creating a service with the Google API always follows the convention new \Google_Service_Xxx($client) where Xxx is the capitalised name of the service we want to use. Therefore, we can easily add a little helper for this:

class Google
{
    // ... 
    
    public function service($service)
    {
        $classname = "Google_Service_$service";

        return new $classname($this->client);
    }
}

🐙 See changes on GitHub

Managing Google accounts

Front-end

Before implementing the OAuth process, let's bootstrap a simple page that lists all Google accounts.

Screen-Shot-2018-08-17-at-13.40.33

I simply ran php artisan make:auth to generate the typical authentication boilerplate, tweaked the navigation and added a new page.

All that's left to do is making our front-end accessible by creating new routes and controllers. Semantically this page calls for a GoogleAccountController with only three actions to list (index), add (store) and delete (destroy) accounts. We do not need an additional create action to request additional data from the user since all data will be collected through the Google API.

class GoogleAccountController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
    }

    public function index()
    {
        return view('accounts', [
            'accounts' => auth()->user()->googleAccounts,
        ]);
    }

    public function store(Request $request, Google $google)
    {
        // TODO:
        // - Handle the OAuth process.
        // - Create a new Google Account.
    }

    public function destroy(GoogleAccount $googleAccount)
    {
        // TODO:
        // - Revoke the authentication token.
        // - Delete the Google Account.
    }
}

Finally, let's define some routes for this controller.

// Managing Google accounts.
Route::name('google.index')->get('google', 'GoogleAccountController@index');
Route::name('google.store')->get('google/oauth', 'GoogleAccountController@store');
Route::name('google.destroy')->delete('google/{googleAccount}', 'GoogleAccountController@destroy');

Note that the store action is using the GET method instead of the typical POST method — for the same reason as before, i.e. we do not request additional data via a form.

🐙 See changes on GitHub

Adding Google accounts

Now that we've got our Google service all set up, it is actually pretty simple to navigate the user through the OAuth process. We start by redirecting the user to the Google's consent screen.

// Remember that we have access to all the methods of the Google’s client through our encapsulation.
return redirect($google->createAuthUrl());

At this point the user is in Google land. Once it has logged in and agreed with the scopes of our application, it is redirected back to our GoogleAccountController@store action except that, this time, we receive a code in the body of our request.

OAuth diagram

// We use Laravel's automatic injections to grasp a fresh instance of the Google service by simply type-hinting it.
public function store(Request $request, Google $google)
{
    if (! $request->has('code')) {
        // Send the user to the OAuth consent screen.
        return redirect($google->createAuthUrl());
    }

    // Do something with $request->get('code');
    // ...

    // Return to the account page.
    return redirect()->route('google.index');
}

We then need to use this code in order to generate an authentication token that we can store in our brand new GoogleAccount instance.

OAuth diagram final

public function store(Request $request, Google $google)
{
    if (! $request->has('code')) {
        return redirect($google->createAuthUrl());
    }

    // Use the given code to authenticate the user.
    $google->authenticate($request->get('code'));
    
    // Make a call to the Google+ API to get more information on the account.
    $account = $google->service('Plus')->people->get('me');

    auth()->user()->googleAccounts()->updateOrCreate(
        [
            // Map the account's id to the `google_id`.
            'google_id' => $account->id,
        ],
        [
            // Use the first email address as the Google account's name.
            'name' => head($account->emails)->value,
            
            // Last but not least, save the access token for later use.
            'token' => $google->getAccessToken(),
        ]
    );

    return redirect()->route('google.index');
}

The updateOrCreate method will first check if there exists a GoogleAccount associated with the authenticated user which also has that google_id. If this is the case, it will simply update the entry instead of creating a new one. This prevents the user from adding multiple times the same Google account but still allows two different users to use the same Google account.

Anyway, this is how it looks 👀

Animated OAuth process

Before we deal with deleting accounts, let's add a little helper method in our Google service to authenticate the client with a given token.

class Google
{
    // ...
    
    public function connectUsing($token)
    {
        $this->client->setAccessToken($token);

        return $this;
    }
}

By writing this method and returning $this, we can make API calls by chaining methods together.

$google
    ->connectUsing($googleAccount->token)
    ->service('Calendar')
    ->...

Google service with token

🐙 See changes on GitHub

Deleting Google accounts

Nothing new here. We've done this countless times.

public function destroy(GoogleAccount $googleAccount)
{
    $googleAccount->delete();

    return redirect()->back();
}

However, we need to tell Google that we will not be using the token associated to this account anymore. This calls for a new method on the Google service.

class Google
{
    //...
    
    public function revokeToken($token = null)
    {
        $token = $token ?? $this->client->getAccessToken();

        return $this->client->revokeToken($token);
    }
}
public function destroy(GoogleAccount $googleAccount, Google $google)
{
    $googleAccount->delete();

    // Event though it has been deleted from our database,
    // we still have access to $googleAccount as an object in memory.
    $google->revokeToken($googleAccount->token);

    return redirect()->back();
}

🐙 See changes on GitHub

Fetching data

Finally, we are all sorted with adding/removing Google accounts and we can start making interesting API calls. For this article we are going to focus on importing everything once. The next couple of articles will deal with how to make sure the imported data stays up-to-date.

Code architecture

Since importing a lot of data from an API is a long procedural process, it can very quickly make your code dirty. Thus, we will extract that logic into several jobs that each take care of a particular resource type. That is:

  • one job that imports Calendars within a given GoogleAccount.
  • one job that imports Events within a given Calendar.

Moreover, the Google API uses an identical process to browse through multiple resources of the same type. So it makes sense for us to implement an abstract super class that orchestrates the logic of browsing through the API whilst the sub classes focus solely on providing the right endpoints and mapping the raw fetched data into a Laravel model.

Here is the architecture that we will be using:

Jobs organization

All jobs are prefixed with Synchronize instead of Fetch because when used multiple times they will update existing resources instead of creating duplicate ones (similar concept to the Google accounts using updateOrCreate() on the google_id column).

Furthermore, in the next articles, these jobs will support synchronization tokens (from the Google API) in order to fetch the changes in Google's data since the last time the job was called.

SynchronizeGoogleResource

Let's start with the abstract class. The Google API allows us to browse through its resources by using pagination tokens. This means, at the end of the first call, if there is more data to fetch, we get a nextPageToken. We then need to do the exact same API call again but this time by adding this token in the request. We carry on like this until nextPageToken is null which means there are no more resources to fetch.

Next page tokens diagram

Let's implement this logic in a job.

namespace App\Jobs;

abstract class SynchronizeGoogleResource
{
    public function handle()
    {
        // Start with an empty page token.
        $pageToken = null;
        
        // Delegate service instantiation to the sub class.
        $service = $this->getGoogleService();

        do {
            // Ask the sub class to perform an API call with this pageToken (initially null).
            $list = $this->getGoogleRequest($service, compact('pageToken'));

            foreach ($list->getItems() as $item) {
                // The sub class is responsible for mapping the data into our database.
                $this->syncItem($item);
            }

            // Get the new page token from the response.
            $pageToken = $list->getNextPageToken();
            
        // Continue until the new page token is null.
        } while ($pageToken);
    }

    abstract public function getGoogleService();
    abstract public function getGoogleRequest($service, $options);
    abstract public function syncItem($item);
}

Now, our two sub classes simply need to implement those three abstract functions.

SynchronizeGoogleCalendars

Here is the skeleton of the job responsible for importing Google calendars.

class SynchronizeGoogleCalendars extends SynchronizeGoogleResource implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $googleAccount;

    public function __construct($googleAccount)
    {
        $this->googleAccount = $googleAccount;
    }

    public function getGoogleService()
    {
        // TODO
    }

    public function getGoogleRequest($service, $options)
    {
        // TODO
    }

    public function syncItem($googleCalendar)
    {
        // TODO
    }
}

The first method simply requires us to boot up a new Google client and generate the service associated to the Google Calendar API.

public function getGoogleService()
{
    return app(Google::class)
        ->connectUsing($this->googleAccount->token)
        ->service('Calendar');
}

The second one is also straightforward and only requires us to do a bit of research in the Google API documentation.

public function getGoogleRequest($service, $options)
{
    return $service->calendarList->listCalendarList($options);
}

The last one is a bit trickier. We could simply map the calendar data provided by Google into a new instance of our Calendar model like this:

public function syncItem($googleCalendar)
{
    $this->googleAccount->calendars()->create([
        'google_id' => $googleCalendar->id,
        'name' => $googleCalendar->summary,
        'color' => $googleCalendar->backgroundColor,
        'timezone' => $googleCalendar->timeZone,
    ]);
}

However, this method is called syncItem, not mapItem. Which means that it will be used all along this series to not only fetch but also update and delete resources. Thus, we must start by upserting our data via the updateOrCreate method.

public function syncItem($googleCalendar)
{
    $this->googleAccount->calendars()->updateOrCreate(
        [
            'google_id' => $googleCalendar->id,
        ],
        [
            'name' => $googleCalendar->summary,
            'color' => $googleCalendar->backgroundColor,
            'timezone' => $googleCalendar->timeZone,
        ]
    );
}

Finally we must find out how to recognise a deleted calendar from Google's API.

Deleted property from CalendarListItem

It turns out that a CalendarListItem has a deleted boolean indicating whether or not the calendar has been deleted.

Note that we use items from the CalendarList and not actual calendars since they provide more information, enable us to iterate through them and convey the actual list of calendars that the user wants to use. Read more about this in the Google documentation.

public function syncItem($googleCalendar)
{
    if ($googleCalendar->deleted) {
        return $this->googleAccount->calendars()
            ->where('google_id', $googleCalendar->id)
            ->get()->each->delete();
    }

    $this->googleAccount->calendars()->updateOrCreate(
        [
            'google_id' => $googleCalendar->id,
        ],
        [
            'name' => $googleCalendar->summary,
            'color' => $googleCalendar->backgroundColor,
            'timezone' => $googleCalendar->timeZone,
        ]
    );
}

SynchronizeGoogleEvents

Very similarly to the previous section, we defined the job responsible for importing Google events from a given Calendar.

class SynchronizeGoogleEvents extends SynchronizeGoogleResource implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
    protected $calendar;

    public function __construct($calendar)
    {
        $this->calendar = $calendar;
    }

    public function getGoogleService()
    {
        return app(Google::class)
            // We access the token through the `googleAccount` relationship.
            ->connectUsing($this->calendar->googleAccount->token)
            ->service('Calendar');
    }

    public function getGoogleRequest($service, $options)
    {
        return $service->events->listEvents(
            // We provide the Google ID of the calendar from which we want the events.
            $this->calendar->google_id, $options
        );
    }

    public function syncItem($googleEvent)
    {
        // A Google event has been deleted if its status is `cancelled`.
        if ($googleEvent->status === 'cancelled') {
            return $this->calendar->events()
                ->where('google_id', $googleEvent->id)
                ->delete();
        }

        $this->calendar->events()->updateOrCreate(
            [
                'google_id' => $googleEvent->id,
            ],
            [
                'name' => $googleEvent->summary,
                'description' => $googleEvent->description,
                'allday' => $this->isAllDayEvent($googleEvent), 
                'started_at' => $this->parseDatetime($googleEvent->start), 
                'ended_at' => $this->parseDatetime($googleEvent->end), 
            ]
        );
    }

    // See next changes on GitHub to check out these helper methods.
    protected function isAllDayEvent($googleEvent) { ... }
    protected function parseDatetime($googleDatetime) { ... }
}

Dispatching the jobs

Okay so now that our jobs are ready to use, let's make sure they are run every time a new GoogleAccount or Calendar is created.

class GoogleAccount extends Model
{
    // ...

    public static function boot()
    {
        parent::boot();

        static::created(function ($googleAccount) {
            SynchronizeGoogleCalendars::dispatch($googleAccount);
        });
    }
}
class Calendar extends Model
{
    // ...

    public static function boot()
    {
        parent::boot();

        static::created(function ($calendar) {
            SynchronizeGoogleEvents::dispatch($calendar);
        });
    }
}

Now you can be sure that when the user adds a Google account, it will start importing all of its calendars, which in turn will start importing all of their associated events.

Note that, when deleting a Google account, all associated data will be deleted only if you used onDelete('cascade') in the relevant relationships of your migrations.

🐙 See changes on GitHub

Viewing events

After all this work, it will be a shame not to see the data we've imported. Therefore let's bootstrap a little page that lists all of our events ordered by descending starting time (from most recent to oldest).

First, we need to create a query that accesses all of the events from a given user. Unfortunately we cannot use the hasManyThrough relationship since the Event model is three levels deep from the User model. That is, we could fetch the calendars (User → GoogleAccount → Calendar) but not one level deeper.

There is actually a package that defines a new Laravel relationship called hasManyDeep which would allow us to reach the Event model from the User model. However, for the purpose of this article, I'd rather not include yet another package. So here is my quick and simple implementation:

class User extends Authenticatable
{
    //...
    
    public function events()
    {
        return Event::whereHas('calendar', function ($calendarQuery) {
            $calendarQuery->whereHas('googleAccount', function ($accountQuery) {
                $accountQuery->whereHas('user', function ($userQuery) {
                    $userQuery->where('id', $this->id);
                });
            });
        });
    }
}

Okay next, let's create an EventController with only one index action.

class EventController extends Controller
{
    public function index()
    {
        $events = auth()->user()->events()
            ->orderBy('started_at', 'desc')
            ->get();

        return view('events', compact('events'));
    }
}

Finally let's define a route for this.

Route::name('event.index')->get('event', 'EventController@index');

If you'd like to see the front-end's code, please check the changes on GitHub below.

Here is what my (made up) calendar events look like on the Google calendar app.

Calendar events in the Google app

Here is what they look like on our application. 🍺

Calendar events in our Laravel app

🐙 See changes on GitHub

Conclusion

When dealing with any API integration (and especially the Google API), it can be very quickly overwhelming. I've tried to make this article as complete as possible so that we can move on to more interesting things on the next ones. I still hope you had fun during that little journey and that you are excited about the new possibilities that your application can now offer.

The next article will deal with synchronizations at regular intervals which means using yet ANOTHER token provided by Google: the syncToken. Instead of giving our existing model the burden of synchronizing themselves we will create a new model that will handle this responsibility.

See ya soon 👋

⌚️ Google Calendar part 2: Periodic synchronizations

Next episode →
Periodic synchronizations

Discussions

Author avatar
Anonymous
3 years ago

Hi there, it works like charm. I just wonder how to ask the user first which calendars to sync, before actual syncing. New to Laravel here :)

💖 0

Discussion

Integrating Google Calendars
Author avatar
Anonymous
3 years ago

Hi there, it works like charm. I just wonder how to ask the user first which calendars to sync, before actual syncing. New to Laravel here :)

💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
Anonymous
3 years ago

I figured it out

💖 0

Discussion

Integrating Google Calendars
Author avatar
Anonymous
3 years ago

I figured it out

💖 0
Author avatar
Loris Leiva
3 years ago

Hi! 👋 Welcome to Laravel! I'm glad you figured it out.

💖 0
Author avatar
Anonymous
3 years ago

Thanks. Do you think there is some smart way to use your code and push events to calendar? Or I need to use G SDK

💖 0
Author avatar
Loris Leiva
3 years ago

I don't see why not. Since we've got OAuth set up, you can use the "google/apiclient" package that we use to make direct calls and push events.

💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
Anonymous
3 years ago

What I do is ... $service = $calendar->googleAccount->getGoogleService('Calendar'); $event = new Google_Service_Calendar_Event(stuff); $service->events->insert($calendar->google_id, $event); It works. Is there anything I should be more careful about? Like tokens expiration or something?

💖 0

Discussion

Integrating Google Calendars
Author avatar
Anonymous
3 years ago

What I do is ... $service = $calendar->googleAccount->getGoogleService('Calendar'); $event = new Google_Service_Calendar_Event(stuff); $service->events->insert($calendar->google_id, $event); It works. Is there anything I should be more careful about? Like tokens expiration or something?

💖 0
Author avatar
Loris Leiva
3 years ago

Awesome! No as long as the token is valid for synchronisation it is valid for pushing events. IIRC these tokens are long lived.

💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
Ali Shahbaz
3 years ago

your article is a big help but i am stuck at a point i.e. // Make a call to the Google+ API to get more information on the account. $account = $google->service('Plus')->people->get('me'); this throws back 403 error and suggests that i should use People API instead as Google API is no longer available.. so can you suggest me its syntax thanks it will be a big help

💖 1

Discussion

Integrating Google Calendars
Author avatar
Ali Shahbaz
3 years ago

your article is a big help but i am stuck at a point i.e. // Make a call to the Google+ API to get more information on the account. $account = $google->service('Plus')->people->get('me'); this throws back 403 error and suggests that i should use People API instead as Google API is no longer available.. so can you suggest me its syntax thanks it will be a big help

💖 1

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
Ali Shahbaz
3 years ago

*** Google plus API is no longer available

💖 1

Discussion

Integrating Google Calendars
Author avatar
Ali Shahbaz
3 years ago

*** Google plus API is no longer available

💖 1
Author avatar
Alkesh Koyande
3 years ago

Use this $google->service('Oauth2')

💖 3
Author avatar
fahdshaykh
3 years ago

@Ali Shahbaz i am also stuck on this point did you getting any solution?

💖 0
Author avatar
Bayram Yılmaz
2 years ago
public function store(Request $request, Google $google)
    {
        if (! $request->has('code')) {
            return redirect($google->createAuthUrl());
        }
       
        $google->authenticate($request->get('code'));

        $account = $google->service('Oauth2');
        $userInfo = $account->userinfo->get();
        
    

        auth()->user()->googleAccounts()->updateOrCreate(
            [
                'google_id' => $userInfo->id,
            ],
            [
                'name' =>$userInfo->email,
                'token' => $google->getAccessToken(),
            ]
        );

        return redirect()->route('google.index');
        
    }
💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
Bayram Yılmaz
2 years ago
    public function store(Request $request, Google $google)
    {
        if (! $request->has('code')) {
            return redirect($google->createAuthUrl());
        }
       
        $google->authenticate($request->get('code'));

        $account = $google->service('Oauth2');
        $userInfo = $account->userinfo->get();
        
    

        auth()->user()->googleAccounts()->updateOrCreate(
            [
                'google_id' => $userInfo->id,
            ],
            [
                'name' =>$userInfo->email,
                'token' => $google->getAccessToken(),
            ]
        );

        return redirect()->route('google.index');
        
    }
💖 0

Discussion

Integrating Google Calendars
Author avatar
Bayram Yılmaz
2 years ago
    public function store(Request $request, Google $google)
    {
        if (! $request->has('code')) {
            return redirect($google->createAuthUrl());
        }
       
        $google->authenticate($request->get('code'));

        $account = $google->service('Oauth2');
        $userInfo = $account->userinfo->get();
        
    

        auth()->user()->googleAccounts()->updateOrCreate(
            [
                'google_id' => $userInfo->id,
            ],
            [
                'name' =>$userInfo->email,
                'token' => $google->getAccessToken(),
            ]
        );

        return redirect()->route('google.index');
        
    }
💖 0
Author avatar
Bayram Yılmaz
2 years ago

Use this code in controller.php I checked the google api package and forums then find solution like that. It working. Now I'll continue to remain of tutorial.

💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
Augusto
2 years ago

I would like to understand when the sync happens after the GoogleAccountController@store completes, as I want to import only one calendar by id, not all the ones I have. Can someone explain to me?

💖 0

Discussion

Integrating Google Calendars
Author avatar
Augusto
2 years ago

I would like to understand when the sync happens after the GoogleAccountController@store completes, as I want to import only one calendar by id, not all the ones I have. Can someone explain to me?

💖 0
Author avatar
Bayram Yılmaz
2 years ago

The calendar sync happens in this method. SynchronizeGoogleCalendars.php If you want to import only one calendar by id. You can specify it in updateOrCreate function in sync method.

💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
Bayram Yılmaz
2 years ago

The calendar sync happens in this method. SynchronizeGoogleCalendars.php If you want to import only one calendar by id. You can specify it in updateOrCreate function in sync method.

💖 0

Discussion

Integrating Google Calendars
Author avatar
Bayram Yılmaz
2 years ago

The calendar sync happens in this method. SynchronizeGoogleCalendars.php If you want to import only one calendar by id. You can specify it in updateOrCreate function in sync method.

💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
Chris
2 years ago

Service Error

It would be interesting to update this article with the disappearance of Google plus.

I have the error: Class 'Google_Service_Plus' not found

How to modify with the new version?

💖 0

Discussion

Integrating Google Calendars
Author avatar
Chris
2 years ago

Service Error

It would be interesting to update this article with the disappearance of Google plus.

I have the error: Class 'Google_Service_Plus' not found

How to modify with the new version?

💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
Dr. Nab Roshyara
2 years ago

Hi, thank you for the wonderul tutorial. I liked it and started to implement google for a social project. Unfortunately I got stucked when it comes to showing the laravel events. I could add a google acount but it did not syncronize the calander and event.

  • I am using laravel 9
  • I have followed your tutorial one by one. Would it be possible for you to implent this whole things for laravel 9 and update your git repo?
💖 0

Discussion

Integrating Google Calendars
Author avatar
Dr. Nab Roshyara
2 years ago

Hi, thank you for the wonderul tutorial. I liked it and started to implement google for a social project. Unfortunately I got stucked when it comes to showing the laravel events. I could add a google acount but it did not syncronize the calander and event.

  • I am using laravel 9
  • I have followed your tutorial one by one. Would it be possible for you to implent this whole things for laravel 9 and update your git repo?
💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
yetki bakadur
6 months ago

I have done everything correctly(I assume), but it's still not working. Another piece of code didn't block me at all, but here we're missing something and I can't find it. { "error": { "code": 401, "message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.", "

💖 0

Discussion

Integrating Google Calendars
Author avatar
yetki bakadur
6 months ago

I have done everything correctly(I assume), but it's still not working. Another piece of code didn't block me at all, but here we're missing something and I can't find it. { "error": { "code": 401, "message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.", "

💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member
Author avatar
Lasse Larsen
4 months ago

This is a great tutorial. I really appreciate you taking the time to not only create a working example, but also teach and share code structure and best practices.

For anyone not able to get this to work I have shared my repo for a working example https://github.com/iamlasse/laravel-google-service It's also doing some other things, but the main functionality based on this tutorial is in the GoogleAccountController, and the volt components.

💖 0

Discussion

Integrating Google Calendars
Author avatar
Lasse Larsen
4 months ago

This is a great tutorial. I really appreciate you taking the time to not only create a working example, but also teach and share code structure and best practices.

For anyone not able to get this to work I have shared my repo for a working example https://github.com/iamlasse/laravel-google-service It's also doing some other things, but the main functionality based on this tutorial is in the GoogleAccountController, and the volt components.

💖 0

Would you like to chime in?

You must be a member to add a reply to a discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member

Would you like to chime in?

You must be a member to start a new discussion.

Fortunately, it only takes two click to become one. See you on the other side! 🌸

Become a Member