Предлагаю начать со структуры проекта, который вы найдёте в репозитории. Если вы хотите написать новый урок, то вам потребуется только содержимое папки web, если же вы будете поддерживать работу экосистемы на сервере, то нужно разобраться и со всем остальным:) но об этом чуть позже.
web
Итак, что мы видим в папке web
? В папке core
расположены "ядро" приложения, а также инструменты, необходимые для написания уроков.
web/core
В папке data
находится файл базы данных. В базе данных хранится информация обо всех, кто сейчас проходит уроки. В папке google_api
вы найдёте инструменты для работы с сервисами Google Sheets и Classroom (как и зачем их применять, обсудим далее). В папке zulip_api
находится скрипт для работы с чатом Zulip. Самое важное — это два файла, db_app.py
и flask_app.py
. Их работа также будет разобрана.
Посмотрим на две оставшиеся папки внутри каталога web
. Это папки, в которых содержатся файлы уроков — по Trello и по GitLab. Для вашего урока вам тоже понадобится создать папку рядом с уже имеющимися.
Важной частью приложения является гугл-таблица. В ней мы описываем сценарии уроков. Удобство такого подхода заключается в том, что сообщения, которые посылаются пользователю, берутся из этой таблицы. Таким образом часть информации выносится из кода и становится лёгко редактируемой.
Для нового урока вы должны создать новый лист в таблице.
Разберём файл db_app.py
. Хранение данных о пользователях осуществляется в основной таблице User и в таблицах уроков. В основной таблице есть поля: email (сюда записывается адрес пользователя; это ключевое поле таблицы) и platform (сюда записывается название урока, который в данный момент проходит пользователь, например, 'git' или 'Trello'). Также используется система отношений один к одному или какая-то другая (спросите у @aaschupak). Это позволяет следить за записями только в основной таблице, не заботясь о записях в дочерних таблицах.
db_app.py
В таблицах TrelloPerson и GitUser хранится информация о пользователе с точки зрения конкретной платформы. Например, в таблице TrelloPerson сохраняются номер доски, id вебхука, текущий этап, список оценок за каждый этап.
Для работы с базой данных мы используем маппер SQLAlchemy.
Создайте новую таблицу для вашего урока. Продумайте, какие поля вам потребуются.
Теперь разберём содержимое папки google_api
.
core/google_api
Нас будут интересовать два файла: GSheetsApi.py
и classroom_integrations.py
. Первый файл содержит функцию, которая возвращает двумерный массив из заданного листа гугл-таблицы, во втором файле находится функция, выставляющая оценку в классрум. Ниже приведены примеры использования:
# range_name это название листа в таблице. следите за тем, чтобы все листы имели уникальные номера
В папке core/zulip_api
нас пока будет интересовать файл send.py
. Из него мы можем импортировать функцию send_message
и посылать сообщения в Zulip от имени бота.
send_message()
# mail="example@miem.hse.ru"
Почти никак) Предполагается, дабы не усложнять жизнь пользователям, не требовать от них никакого ввода в чат. Все триггеры должны срабатывать автоматически, в зависимости от тех или иных действий пользователя в конкретной среде.
Однако вам необходимо обеспечить корректную обработку двух команд, которые могут поступать от пользователя в чате. Это stop
и restart
. Нетрудно догадаться, что первая команда должна завершать урок (и выставлять оценку за то, что пользователь успел сделать), а вторая — перезапускать урок.
Каким образом команда от пользователя доходит до вашего урока? Для этого запущен модуль, который постоянно слушает сообщения, которые пользователи присылают боту, и если какое-то из них совпадает с stop
или restart
, то он делает соответствующий запрос основному приложению. Ваша задача — корректно его обработать. О том, как это сделать, написано ниже.
Для этого есть два способа: запросить эту информацию у какого-нибудь сервиса, где сейчас, предположительно, проводит время пользователь, или настроить этот сервис таким образом, чтобы он сам присылал вам некоторый запрос, как только пользователь что-то сделал. Первый способ предполагает (сначала изучение, а потом) использование API-запросов выбранного вами сервиса, а второй способ обеспечивается так называемыми вебхуками (или Webhooks).
Как выбрать, чем пользоваться? Скорее всего, вам придётся скомбинировать оба подхода (как минимум, послать API-запрос, который настроит вам вебхуки). В целом второй подход предпочтительнее, чем первый, ведь вам не нужно делать запросы много запросов (иногда это даже может привести к проблемам у сервиса). Но не всегда есть возможность для использования вебхуков.
Для работы с сервисами вам понадобится создать api-токены или ключи. Делайте это от имени сервисного аккаунта, который создан для нашего проекта, — onlineeducation@miem.hse.ru. Данные от аккаунта найдёте в приватном репозитории.
Технически связь с внешним миром обеспечивается с помощью библиотеки Requests (для отправки любых http-запросов — GET, POST, DELETE и так далее) и фреймворка Flask. Последний нужен для того, чтобы наше приложение могло принимать запросы (вебхуки) от внешних сервисов. Также средствами фласка отслеживаются пригласительная ссылка, по которой пользователи начинают урок, и запросы на stop
и restart
, которые мы уже упоминали.
Теперь самое время разобрать файл flask_app.py
.
core/flask_app.py
В начале файла происходит импорт сторонних библиотек (например, flask и Requests), а также логика уроков (например, в строке 12 импортируется фласк-модуль git (так называемый Blueprint), а также две функции: start_git
и end_git
. Подобным образом вам нужно будет импортировать ваш урок. Далее происходит инициализация различных вещей. В строках 28 и 29 вы можете увидеть, как зарегистрировать ваш модуль фласка (в нашем примере git или trello) в основном фласке.
Видно, что все ссылки, которые относятся к модулю, имеют заданный префикс (например, все ссылки вида /git/***
будут обрабатываться в модуле git). При этом внутри модуля в маршрутах префикс /git
опускается.
stop
и restart
Далее мы видим строчку с декоратором (@app.route('/service')
). Так во фласке объявляются URL, которые мы собираемся обслуживать. Например, с помощью этого декоратора мы будем прослушивать http://localhost:5000/service
. Это как раз маршрут, по которому приходят сервисные сообщения stop
и restart
.
Модуль, который читает сообщения пользователей, посылает по этому адресу GET-запрос с аргументами — mail и command. Когда к нам приходит этот запрос, мы можем из него получить эти аргументы (строки 63-64).
core/flask_app.py
Далее мы обращаемся к базе данных, чтобы узнать, на каком уроке находится пользователь (если находится). Узнав это (строки 39, 44), мы можем вызвать соответствующие функции. Если пришёл запрос на перезапуск, вызываем функцию restart(mail, platform)
, если на остановку, запускаем stop(mail, platform)
.
restart
и stop
Разберём, например, в функции restart
в строках 34-39 перезапуск урока по Трелло. Мы удаляем старую доску, а затем начинаем урок заново, то есть создаём новую команду в трелло, в этой команде создаём новую доску, запоминаем id этих объектов в базе данных, приглашаем пользователя на эту доску, обращаемся к гугл-таблице и посылаем пользователю сообщение с заданием.
Следующие два роутинга реализуют запуск урока через авторизацию в Google. Мы пользуемся Google OAuth для того, чтобы получить электронный адрес пользователя, а заодно проверить, что пользователь имеет к нему доступ. Кроме того, это безопаснее, чем передавать адрес внутри URL.
core/flask_app.py
Разберём строки со 115 по 146. Это последний этап авторизации, когда пользователь уже нажал кнопку "Войти". Мы получаем платформу и электорнный адрес и проверяем, что он заканчивается на @miem.hse.ru
. Далее происходит остановка запущенного урока, если он уже был (функция stop
). Далее запускаем новый урок (например, вызовом функций trello.create_environment(mail)
или start_git(mail=mail)
; их мы импоритровали в начале файла).
Для отладки предлагаем вам использовать библиотеку logging. Логи пишутся в файл core/logs/core.txt
. Чтобы посмотреть логи, не заходя на сервер, достаточно перейти по адресу /log
. Там вы увидите все логи, записанные в файл.
Для этого в flask_app.py
предусмотрен маршрут @app.route('/log')
Файл с логами стирается вместе с перезапуском сервера.
В дальшейшем планирую автоматически выгружать логи на гугл-диск, например, раз в сутки.
Теперь, когда вы познакомились со всем, что помогает урокам работать, можно поговорить о том, что представляют собой сами уроки. В целом, это последовательность действий, которые предлагается выполнить пользователю на одной или нескольких платформах. Взаимодействие между пользователем и приложением происходит интерактивно: сначала пользователю присылается первый шаг задания, дальше пользователь пытается его выполнить, как только у него это получается (возможно, с некоторыми ошибками), присылается следующий шаг, и так далее. Таким образом в уроке нужно:
Советы: