Как-то неожиданно для меня подключение своего nodejs-приложения к Google Calendar API оказалось довольно нетривиальной задачей. Несмотря на подробное описание вариантов подключения на русском языке пришлось продираться через лес различных настроек и конфигураций. В статье подробно изложены шаги, которые приходится совершить, чтобы интеграция завершилась успехом.
Цель интеграции - дать возможность nodejs-приложению публиковать события в определённый календарь. В данном примере использовался обычная, персональная учётная запись в Google.
Создание календаря
Для начала нужно создать календарь, в который будем публиковать события. Идём в Google Calendar и нажимаем на кнопку "+" рядом с "Другие календари", затем выбираем пункт "Создать календарь":
заполняем форму и опять жмём "Создать календарь", но уже синюю кнопку:
Гугл довольно долго шуршит мозгами, после чего радостно сообщает, что новый календарь готов. Доступ к настройкам нового календаря:
В настройках нас прежде всего интересует пункт "Интеграция календаря":
в котором самое полезное - это "Идентификатор календаря"
c093hr4fqjuj5k9e6uvvac73ac@group.calendar.google.com
Этот значение наше nodejs-приложение будет использовать при подключении к API.
Регистрация приложения в Google
Управление приложениями находится в "Консоли API".
Создать новое приложение можно через селектор текущего приложения - "Выберите проект":
После создания проект становится доступным в списке проектов - "Habr Demo":
Теперь нужно дать доступ этому проекту к соответствующим API Google'а:
Так как интерфейсов Google наплодил свыше 3 сотен, используем фильтр для поиска нужных:
Фильтрация по ключу "calend" оставляет всего два вариант, один из которых нам и нужен:
Заходим в "Google Calendar API" и включаем его для использования в нашем проекте:
После чего проваливаемся в dashboard использования этого API в нашем проекте (https://console.developers.google.com/apis/api/calendar-json.googleapis.com/overview?project=habr-demo-293107&supportedpurview=project), где нам сообщают, что для использования API мы должны создать учётные данные:
Жмём "СОЗДАТЬ УЧЁТНЫЕ ДАННЫЕ" и заполняем анкету, определяющую, какой тип учётных данных нам нужен для доступа к API:
вся анкета не влезла в один скрин, вот остаток:
По результатам анкетирования Google предлагает создать закрытый ключ в формате JSON, при помощи которого наше приложение будет подключаться к Google Calendar API:
Самую большую трудность для меня создал пункт "Роль". Похоже, что ролей у Google'а даже больше, чем API, к которым можно подключаться:
В общем, если выбрать "Проект / Владелец", то обещают предоставить полный доступ ко всем ресурсам. Жмём на кнопку "Продолжить" и получаем JSON-файл в каталог "Downloads" на своём компрьютере:
Содержимое JSON-файла примерно такое (ключ я вырезал):
{
"type": "service_account",
"project_id": "habr-demo-293107",
"private_key_id": "4ec17ea5f8b606e0535a0623a110111123fd3c33",
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
"client_email": "nodejs-app@habr-demo-293107.iam.gserviceaccount.com",
"client_id": "102219376121816220804",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/nodejs-app%40habr-demo-293107.iam.gserviceaccount.com"
}
В консоли API у приложения "Habr Demo" появляется новый сервис с email'ом "nodejs-app@habr-demo-293107.iam.gserviceaccount.com":
Код
Для подключения к API из nodejs-приложения Google предлагает библиотеку googleapis и обзор её использования. В обзоре применяется OAuth2-аутентификация и получение из календаря списка событий, а нам нужна аутентификация по ключу в соответствующих scope'ах и добавление события в календарь. Поэтому код получается примерно такой:
const fs = require('fs');
const {google} = require('googleapis');
const CALENDAR_ID = 'c093hr4fqjuj5k9e6uvvac73ac@group.calendar.google.com';
const KEYFILE = 'Habr Demo-4ec17ea5f8b6.json'; // path to JSON with private key been downloaded from Google
const SCOPE_CALENDAR = 'https://www.googleapis.com/auth/calendar'; // authorization scopes
const SCOPE_EVENTS = 'https://www.googleapis.com/auth/calendar.events';
(async function run() {
// INNER FUNCTIONS
async function readPrivateKey() {
const content = fs.readFileSync(KEYFILE);
return JSON.parse(content.toString());
}
async function authenticate(key) {
const jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
[SCOPE_CALENDAR, SCOPE_EVENTS]
);
await jwtClient.authorize();
return jwtClient;
}
async function createEvent(auth) {
const event = {
'summary': 'Habr Post Demo',
'description': 'Тест для демонстрации интеграции nodejs-приложения с Google Calendar API.',
'start': {
'dateTime': '2020-10-20T16:00:00+02:00',
'timeZone': 'Europe/Riga',
},
'end': {
'dateTime': '2020-10-20T18:00:00+02:00',
'timeZone': 'Europe/Riga',
}
};
let calendar = google.calendar('v3');
await calendar.events.insert({
auth: auth,
calendarId: CALENDAR_ID,
resource: event,
});
}
// MAIN
try {
const key = await readPrivateKey();
const auth = await authenticate(key);
await createEvent(auth);
} catch (e) {
console.log('Error: ' + e);
}
})();
Запускаем код на выполнение и получаем ответ от Calendar API:
{
...
"status": 404,
"statusText": "Not Found",
...
}
что и логично - наш новый календарь ничего не знает про наш новый сервис.
Настройка доступа сервиса к календарю
Заходим в наш календарь, созданный ранее, и в настройках жмём "Доступ для отдельных пользователей", затем "Добавить пользователей" и вводим email-адрес нашего сервиса, который будет создавать события в нашем календаре:
После предоставления сервису доступа к календарю нужно настроить права:
Выбираем "Внесение изменений и предоставление доступа", иначе будем иметь ошибку "You need to have writer access to this calendar." при обращении к API:
Запускаем наш скрипт на выполнение ещё раз и фиксируем результат в календаре:
В коде я задавал время старта события в 16:00:
'start': {
'dateTime': '2020-10-20T16:00:00+02:00',
'timeZone': 'Europe/Riga',
}
a по факту событие запланировано на 17:00, что вполне соответствует духу IT:
В программировании существует лишь два характерных затруднения: инвалидация кеша, наименование сущностей и ошибка на единицу
Резюме
Вот и всё, квест пройден. Хэппи, как говорится, кодинг.