Repository: amp-labs/connectors
Pull request #134 (2024-03-28 15:47:31)

feat: Add Google Calendar connector
Closes https://github.com/amp-labs/connectors/issues/135

## Checklist
- [x] Ran Linter
- [x] Catalog tests passing

## Catalog variables
scopes: `https://www.googleapis.com/auth/calendar`

## Notes


## Testing

### GET
URL: http://localhost:4444/v3/users/me/calendarList
List all calendars that belong to user
![image](https://github.com/amp-labs/connectors/assets/60330780/969565c4-b021-4223-bf65-de6e46d586a0)
Go to google calendar and create an event for yourself, we will use it in the next set of requests. Back to postman using id of one of those calendars, retrieve events of that calendar, then query single event by that id.
URL: http://localhost:4444/v3/calendars/dobrbobr2022@gmail.com/events/7pv3h8pq79p0lga9v46c3ju2mp
![image](https://github.com/amp-labs/connectors/assets/60330780/527e80f7-8b76-4723-9a23-6b68970cca96)


### PATCH
We can change the end of an event to last longer, things immidietly are reflected in calendar.
URL: http://localhost:4444/v3/calendars/dobrbobr2022@gmail.com/events/7pv3h8pq79p0lga9v46c3ju2mp
![image](https://github.com/amp-labs/connectors/assets/60330780/178a1055-a6d4-4c27-8c9e-04b86138f7dc)
![image](https://github.com/amp-labs/connectors/assets/60330780/fbc91dcf-f2be-4818-bed5-4bf808bbbe51)
![image](https://github.com/amp-labs/connectors/assets/60330780/3c71158f-dacf-456b-ace0-11b085619b42)

### PUT
Unlike PATCH it must include the whole payload, otherwise you will get `400`.
![image](https://github.com/amp-labs/connectors/assets/60330780/d97dded5-8b6b-44d2-a7d8-e565ce4820dc)
If you specify both start and end of an event PUT will work.
URL: http://localhost:4444/v3/calendars/dobrbobr2022@gmail.com/events/7pv3h8pq79p0lga9v46c3ju2mp
![image](https://github.com/amp-labs/connectors/assets/60330780/14eb5f47-b3b4-4c9b-a31d-f9663160f376)
![image](https://github.com/amp-labs/connectors/assets/60330780/a7a06af7-28ec-4874-820f-76c31bb7a424)


### DELETE
Remove calendar event.
URL: http://localhost:4444/v3/calendars/dobrbobr2022@gmail.com/events/7pv3h8pq79p0lga9v46c3ju2mp
![image](https://github.com/amp-labs/connectors/assets/60330780/c2bc3444-43f4-469b-bed9-6ee4a2420b6c)


## Pagination
Example paginates calendar holidays of Ukraine. We are intereseted in partial request with cursor and holiday names only.

### First page
GET http://localhost:4444/v3/calendars/en.ukrainian%23holiday%40group.v.calendar.google.com/events?maxResults=2&fields=items/summary,nextPageToken
![image](https://github.com/amp-labs/connectors/assets/60330780/37879fdd-2ff2-4a03-b696-020d18271357)

### Next page
![image](https://github.com/amp-labs/connectors/assets/60330780/b755ca26-1ec1-4fe4-9084-1247f54ef250)

### Last page
Starting from second page we can increase the page size. As a result `nextPageToken` is gone from response.
![image](https://github.com/amp-labs/connectors/assets/60330780/bd0e6314-5131-4404-ae50-f1dbceed3c68)





Patch:
diff --git a/providers/catalog.go b/providers/catalog.go
index f9e19872..6e0de0ec 100644
--- a/providers/catalog.go
+++ b/providers/catalog.go
@@ -23,7 +23,7 @@ const (
 	Gong                                Provider = "gong"
 	Zoom                                Provider = "zoom"
 	Intercom                            Provider = "intercom"
-  	Capsule                             Provider = "capsule"
+	Capsule                             Provider = "capsule"
 	DocuSign                            Provider = "docuSign"
 	DocuSignDeveloper                   Provider = "docuSignDeveloper"
 	Calendly                            Provider = "calendly"
@@ -33,6 +33,7 @@ const (
 	MicrosoftDynamics365Sales           Provider = "microsoftDynamics365Sales"
 	MicrosoftDynamics365BusinessCentral Provider = "microsoftDynamics365BusinessCentral"
 	Gainsight                           Provider = "gainsight"
+	GoogleCalendar                      Provider = "googleCalendar"
 )
 
 // ================================================================================
@@ -590,4 +591,25 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			Write:     false,
 		},
 	},
+
+	GoogleCalendar: {
+		AuthType: Oauth2,
+		BaseURL:  "https://www.googleapis.com/calendar",
+		OauthOpts: OauthOpts{
+			AuthURL:                   "https://accounts.google.com/o/oauth2/v2/auth",
+			TokenURL:                  "https://oauth2.googleapis.com/token",
+			ExplicitScopesRequired:    true,
+			ExplicitWorkspaceRequired: false,
+			TokenMetadataFields: TokenMetadataFields{
+				ScopesField: "scope",
+			},
+		},
+		Support: Support{
+			BulkWrite: false,
+			Proxy:     false,
+			Read:      false,
+			Subscribe: false,
+			Write:     false,
+		},
+	},
 }
diff --git a/scripts/oauth/token.go b/scripts/oauth/token.go
index 0ecd2fb9..5ab69d63 100644
--- a/scripts/oauth/token.go
+++ b/scripts/oauth/token.go
@@ -242,6 +242,7 @@ func setup() *OAuthApp {
 	provider := registry.MustString("Provider")
 	clientId := registry.MustString("ClientId")
 	clientSecret := registry.MustString("ClientSecret")
+
 	state, err := registry.GetString("State")
 	if err != nil {
 		slog.Warn("no state attached, ensure that the provider doesn't require state")


Test patch:
diff --git a/providers/catalog_test.go b/providers/catalog_test.go
index cfc1cbb6..68ba98bd 100644
--- a/providers/catalog_test.go
+++ b/providers/catalog_test.go
@@ -692,6 +692,31 @@ var testCases = []struct { // nolint
 		},
 		expectedErr: nil,
 	},
+	{
+		provider:    GoogleCalendar,
+		description: "Google Calendar provider config with no substitutions",
+		expected: &ProviderInfo{
+			Support: Support{
+				Read:      false,
+				Write:     false,
+				BulkWrite: false,
+				Subscribe: false,
+				Proxy:     false,
+			},
+			AuthType: Oauth2,
+			OauthOpts: OauthOpts{
+				AuthURL:                   "https://accounts.google.com/o/oauth2/v2/auth",
+				TokenURL:                  "https://oauth2.googleapis.com/token",
+				ExplicitWorkspaceRequired: false,
+				ExplicitScopesRequired:    true,
+				TokenMetadataFields: TokenMetadataFields{
+					ScopesField: "scope",
+				},
+			},
+			BaseURL: "https://www.googleapis.com/calendar",
+		},
+		expectedErr: nil,
+	},
 }
 
 func TestReadInfo(t *testing.T) { // nolint