-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Ivan Kudinov
committed
Jun 11, 2024
1 parent
49e18f4
commit 0e609f0
Showing
165 changed files
with
48,524 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,44 @@ | ||
# next-feature-smartapp | ||
|
||
Бот для тестирования функционала BotX | ||
## Отладка Backend | ||
|
||
### Установка | ||
#### Конфигурирование | ||
Необходимо скопировать `./backend/example.env -> ./backend/.env`. Затем заполнить переменную BOT_CREDENTIALS в файле `./backend/.env` в формате `<host>@<secret_key>@<bot_id>`. | ||
|
||
1. Воспользуйтесь инструкцией [Руководство администратора](https://express.ms/admin_guide.pdf) | ||
`-> Эксплуатация корпоративного сервера -> Управление контактами -> Чат-боты` | ||
2. Создайте бота в панели администратора eXpress. | ||
3. Получите `secret_key` (Секретный ключ) и `bot_id` (ID) нажав на имя созданного бота. | ||
4. Получите `cts_host` имя хоста, на котором находится панель администратора. | ||
5. Установите флаг "Включено" и заполните имя для SmartApp. | ||
|
||
6. Скачайте репозиторий на сервер: | ||
Для получения параметров `secret_key` и `bot_id` необходимо создать бота в панели администратора (см. *[Руководство администратора](https://express.ms/admin_guide.pdf) -> Эксплуатация корпоративного сервера -> Управление контактами -> Чат-боты*). | ||
|
||
#### Запуск в режиме отладки | ||
```bash | ||
git clone --recursive https://github.com/ExpressApp/next-feature-smartapp | ||
docker-compose up -d | ||
``` | ||
|
||
флаг `--recursive` нужен для клонирования подмодуля botx-smartapp-rpc | ||
## Отладка Frontend | ||
|
||
7. Заполните переменные окружения в `docker-compose.yml` реальными значениями: | ||
#### Установка зависимостей | ||
``` | ||
cd frontend | ||
npm i | ||
``` | ||
|
||
#### Запуск в режиме отладки | ||
``` | ||
- BOT_CREDENTIALS= | ||
./scripts/run-front | ||
``` | ||
|
||
8. Запустите контейнеры командой: | ||
## Деплой на сервер | ||
|
||
```bash | ||
docker-compose up -d | ||
#### Сборка контейнера | ||
``` | ||
9. Убедитесь, что в логах нет ошибок. | ||
|
||
```bash | ||
docker-compose logs | ||
./scripts/build | ||
``` | ||
|
||
10. Выберите текущий SmartApp через меню SmartApp, проверьте что он открылся. | ||
#### Запуск | ||
``` | ||
docker run -d \ | ||
-e BOT_CREDENTIALS="<creds>" \ | ||
-e DEBUG=True \ | ||
<tag> | ||
``` | ||
гдe | ||
`<tag>` - название тега, собранного на предыдущем шаге. | ||
`<creds>` - учетные данные бота в формате `cts_host@secret_key@bot_id` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
**/__pycache__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
# Byte-compiled / optimized / DLL files | ||
__pycache__/ | ||
*.py[cod] | ||
*$py.class | ||
|
||
# C extensions | ||
*.so | ||
|
||
# Distribution / packaging | ||
.Python | ||
build/ | ||
develop-eggs/ | ||
dist/ | ||
downloads/ | ||
eggs/ | ||
.eggs/ | ||
lib/ | ||
lib64/ | ||
parts/ | ||
sdist/ | ||
var/ | ||
wheels/ | ||
*.egg-info/ | ||
.installed.cfg | ||
*.egg | ||
MANIFEST | ||
|
||
# PyInstaller | ||
# Usually these files are written by a python script from a template | ||
# before PyInstaller builds the exe, so as to inject date/other infos into it. | ||
*.manifest | ||
*.spec | ||
|
||
# Installer logs | ||
pip-log.txt | ||
pip-delete-this-directory.txt | ||
|
||
# Unit test / coverage reports | ||
htmlcov/ | ||
.tox/ | ||
.coverage | ||
.coverage.* | ||
.cache | ||
nosetests.xml | ||
coverage.xml | ||
*.cover | ||
.hypothesis/ | ||
.pytest_cache/ | ||
|
||
# Translations | ||
*.mo | ||
*.pot | ||
|
||
# Django stuff: | ||
*.log | ||
local_settings.py | ||
db.sqlite3 | ||
|
||
# Flask stuff: | ||
instance/ | ||
.webassets-cache | ||
|
||
# Scrapy stuff: | ||
.scrapy | ||
|
||
# Sphinx documentation | ||
docs/_build/ | ||
|
||
# PyBuilder | ||
target/ | ||
|
||
# Jupyter Notebook | ||
.ipynb_checkpoints | ||
|
||
# pyenv | ||
.python-version | ||
|
||
# celery beat schedule file | ||
celerybeat-schedule | ||
|
||
# SageMath parsed files | ||
*.sage.py | ||
|
||
# Environments | ||
.env | ||
.venv | ||
env/ | ||
venv/ | ||
ENV/ | ||
env.bak/ | ||
venv.bak/ | ||
|
||
# Spyder project settings | ||
.spyderproject | ||
.spyproject | ||
|
||
# Rope project settings | ||
.ropeproject | ||
|
||
# mkdocs documentation | ||
/site | ||
|
||
# mypy | ||
.mypy_cache/ | ||
|
||
.idea/ | ||
.vscode/ | ||
|
||
# frontend build | ||
app/resources/static |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
FROM python:3.10-slim | ||
|
||
ENV PYTHONUNBUFFERED 1 | ||
ENV UVICORN_CMD_ARGS "" | ||
|
||
ARG NON_ROOT_USER=express_bot | ||
RUN useradd --create-home ${NON_ROOT_USER} | ||
|
||
EXPOSE 8000 | ||
WORKDIR /app | ||
|
||
COPY poetry.lock pyproject.toml ./ | ||
|
||
ARG CI_JOB_TOKEN="" | ||
ARG GIT_HOST="" | ||
ARG GIT_PASSWORD=${CI_JOB_TOKEN} | ||
ARG GIT_LOGIN="gitlab-ci-token" | ||
|
||
RUN apt-get update && \ | ||
apt-get install -y sudo git build-essential && \ | ||
echo "${NON_ROOT_USER} ALL = NOPASSWD: /usr/sbin/update-ca-certificates" > /etc/sudoers.d/express_bot && \ | ||
apt-get clean autoclean && \ | ||
apt-get autoremove --yes && \ | ||
rm -rf /var/lib/{apt,dpkg,cache,log}/ | ||
|
||
# Poetry can't read password to download private repos | ||
RUN echo -e "machine ${GIT_HOST}\nlogin ${GIT_LOGIN}\npassword ${GIT_PASSWORD}" > ~/.netrc && \ | ||
pip install poetry==1.5.1 --no-cache-dir && \ | ||
poetry config virtualenvs.create false && \ | ||
poetry install --no-dev && \ | ||
rm -rf /root/.cache/pypoetry && \ | ||
rm -rf ~/.netrc | ||
|
||
COPY app app | ||
|
||
ARG CI_COMMIT_SHA="" | ||
ENV GIT_COMMIT_SHA=${CI_COMMIT_SHA} | ||
|
||
USER ${NON_ROOT_USER} | ||
|
||
CMD sudo update-ca-certificates && \ | ||
uvicorn --host=0.0.0.0 $UVICORN_CMD_ARGS app.main:app |
Empty file.
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
"""Bot dependency for FastAPI.""" | ||
|
||
from fastapi import Depends, Request | ||
from pybotx import Bot | ||
|
||
|
||
def get_bot(request: Request) -> Bot: | ||
assert isinstance(request.app.state.bot, Bot) | ||
|
||
return request.app.state.bot | ||
|
||
|
||
bot_dependency = Depends(get_bot) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
"""Endpoints for communication with botx.""" | ||
|
||
from http import HTTPStatus | ||
|
||
from fastapi import APIRouter, Request | ||
from fastapi.responses import JSONResponse | ||
from pybotx import ( | ||
Bot, | ||
BotXMethodCallbackNotFoundError, | ||
UnknownBotAccountError, | ||
UnsupportedBotAPIVersionError, | ||
UnverifiedRequestError, | ||
build_bot_disabled_response, | ||
build_command_accepted_response, | ||
build_unverified_request_response, | ||
) | ||
from pybotx.constants import BOT_API_VERSION | ||
|
||
from app.api.dependencies.bot import bot_dependency | ||
from app.logger import logger | ||
from app.settings import settings | ||
|
||
router = APIRouter() | ||
|
||
|
||
@router.post("/command") | ||
async def command_handler(request: Request, bot: Bot = bot_dependency) -> JSONResponse: | ||
"""Receive commands from users. Max timeout - 5 seconds.""" | ||
try: # noqa: WPS225 | ||
bot.async_execute_raw_bot_command( | ||
await request.json(), | ||
request_headers=request.headers, | ||
) | ||
except ValueError: | ||
error_label = "Bot command validation error" | ||
|
||
if settings.DEBUG: | ||
logger.exception(error_label) | ||
else: | ||
logger.warning(error_label) | ||
|
||
return JSONResponse( | ||
build_bot_disabled_response(error_label), | ||
status_code=HTTPStatus.SERVICE_UNAVAILABLE, | ||
) | ||
except UnknownBotAccountError as exc: | ||
error_label = f"No credentials for bot {exc.bot_id}" | ||
logger.warning(error_label) | ||
|
||
return JSONResponse( | ||
build_bot_disabled_response(error_label), | ||
status_code=HTTPStatus.SERVICE_UNAVAILABLE, | ||
) | ||
except UnsupportedBotAPIVersionError as exc: | ||
error_label = ( | ||
f"Unsupported Bot API version: `{exc.version}`. " | ||
f"Set protocol version to `{BOT_API_VERSION}` in Admin panel." | ||
) | ||
logger.warning(error_label) | ||
|
||
return JSONResponse( | ||
build_bot_disabled_response(error_label), | ||
status_code=HTTPStatus.SERVICE_UNAVAILABLE, | ||
) | ||
except UnverifiedRequestError as exc: | ||
logger.warning(f"UnverifiedRequestError: {exc.args[0]}") | ||
return JSONResponse( | ||
content=build_unverified_request_response( | ||
status_message=exc.args[0], | ||
), | ||
status_code=HTTPStatus.UNAUTHORIZED, | ||
) | ||
|
||
return JSONResponse( | ||
build_command_accepted_response(), status_code=HTTPStatus.ACCEPTED | ||
) | ||
|
||
|
||
@router.get("/status") | ||
async def status_handler(request: Request, bot: Bot = bot_dependency) -> JSONResponse: | ||
"""Show bot status and commands list.""" | ||
try: | ||
status = await bot.raw_get_status( | ||
dict(request.query_params), | ||
request_headers=request.headers, | ||
) | ||
except UnknownBotAccountError as exc: | ||
error_label = f"Unknown bot_id: {exc.bot_id}" | ||
logger.warning(exc) | ||
return JSONResponse( | ||
build_bot_disabled_response(error_label), | ||
status_code=HTTPStatus.SERVICE_UNAVAILABLE, | ||
) | ||
except ValueError: | ||
error_label = "Invalid params" | ||
logger.warning(error_label) | ||
return JSONResponse( | ||
build_bot_disabled_response(error_label), status_code=HTTPStatus.BAD_REQUEST | ||
) | ||
except UnverifiedRequestError as exc: | ||
logger.warning(f"UnverifiedRequestError: {exc.args[0]}") | ||
return JSONResponse( | ||
content=build_unverified_request_response( | ||
status_message=exc.args[0], | ||
), | ||
status_code=HTTPStatus.UNAUTHORIZED, | ||
) | ||
|
||
return JSONResponse(status) | ||
|
||
|
||
@router.post("/notification/callback") | ||
async def callback_handler(request: Request, bot: Bot = bot_dependency) -> JSONResponse: | ||
"""Process BotX methods callbacks.""" | ||
try: | ||
await bot.set_raw_botx_method_result( | ||
await request.json(), | ||
verify_request=False, | ||
) | ||
except BotXMethodCallbackNotFoundError as exc: | ||
error_label = f"Unexpected callback with sync_id: {exc.sync_id}" | ||
logger.warning(error_label) | ||
|
||
return JSONResponse( | ||
build_bot_disabled_response(error_label), | ||
status_code=HTTPStatus.SERVICE_UNAVAILABLE, | ||
) | ||
|
||
return JSONResponse( | ||
build_command_accepted_response(), | ||
status_code=HTTPStatus.ACCEPTED, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
"""Configuration of routers for all endpoints.""" | ||
from fastapi import APIRouter | ||
|
||
from app.api.endpoints.botx import router as bot_router | ||
|
||
router = APIRouter() | ||
|
||
router.include_router(bot_router, tags=["botx"]) |
Empty file.
Oops, something went wrong.