Как-то неожиданно для меня подключение своего 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:

В программировании существует лишь два характерных затруднения: инвалидация кеша, наименование сущностей и ошибка на единицу

Резюме

Вот и всё, квест пройден. Хэппи, как говорится, кодинг.

Ссылки