Skip to content

Commit 91381d6

Browse files
AuthFaileddependabot[bot]github-advanced-security[bot]
authored
Пуш изменений dev ветки (#100)
* feat(dialogs): Внедрение базового функционала пользователя с использованием aiogram-dialog Signed-off-by: Roman Chursanov <[email protected]> * feat(schedule): Возможность перелистывания графика дежурных и руководителей Signed-off-by: Roman Chursanov <[email protected]> * fix(schedule): Исправление открытия текущего дня в графиках Signed-off-by: Roman Chursanov <[email protected]> * feat(kpi): Меню показателей на aiogram-dialog для специалистов Signed-off-by: Roman Chursanov <[email protected]> * feat(docker): Открытие redis в мир Signed-off-by: Roman Chursanov <[email protected]> * feat(kpi): Меню показателей на aiogram-dialog для руководителей Signed-off-by: Roman Chursanov <[email protected]> * feat(cmds): Подсказка команде /whois Signed-off-by: Roman Chursanov <[email protected]> * feat(shop): Магазин на aiogram-dialog для специалистов Signed-off-by: Roman Chursanov <[email protected]> * feat(inventory): Инвентарь на aiogram-dialog для специалистов Signed-off-by: Roman Chursanov <[email protected]> * refactor(states): Классификация состояний специалистов Signed-off-by: Roman Chursanov <[email protected]> * refactor: Разделение игровых окон диалога на папки Signed-off-by: Roman Chursanov <[email protected]> * feat(shop): Активация предметов через меню магазина Signed-off-by: Roman Chursanov <[email protected]> * feat(achievements): Меню достижений для специалистов Фильтры по должности и периоду начисления достижений Signed-off-by: Roman Chursanov <[email protected]> * feat(achievements): Меню истории баланса Фильтры по типу и источнику транзакции Signed-off-by: Roman Chursanov <[email protected]> * refactor(menu): Удаление неактуальных блоков кода В связи с переездом на aiogram-dialog Signed-off-by: Roman Chursanov <[email protected]> * feat(search): Меню поиска для специалистов Signed-off-by: Roman Chursanov <[email protected]> * feat(search): Меню деталей сотрудника из поиска для специалистов Signed-off-by: Roman Chursanov <[email protected]> * refactor(schedule): Рефакторинг использования ScheduleHandlerService Signed-off-by: Roman Chursanov <[email protected]> * feat(schedule): Поддержка детального режима графика для специалистов Signed-off-by: Roman Chursanov <[email protected]> * feat(search): Поиск по запросу для специалистов Signed-off-by: Roman Chursanov <[email protected]> * feat(search): Унифицирование окон поиска Signed-off-by: Roman Chursanov <[email protected]> * feat(activation): Унифицированная активация предметов для МИП, ГОК и дежурных Signed-off-by: Roman Chursanov <[email protected]> * feat(activation): Унифицированные окна предметов и достижений для ГОК, МИП и специалистов Signed-off-by: Roman Chursanov <[email protected]> * fix(achievements): Исправлено отображение фильтра по должности для специалистов и дежурных Signed-off-by: Roman Chursanov <[email protected]> * feat(game): Фильтры для игровых меню для ГОК и МИП Signed-off-by: Roman Chursanov <[email protected]> * style(game): Текст для игрового меню Signed-off-by: Roman Chursanov <[email protected]> * feat(schedule): Меню графиков для руководителей + Рефакторинг неактуального кода Signed-off-by: Roman Chursanov <[email protected]> * fix: Возвращен обработчик старта для МИП Signed-off-by: Roman Chursanov <[email protected]> * refactor(docstrings): Большой рефакторинг №1 Большая часть рефакторинга покрывает код докстрингами в формате Google. Частично добавлен функционал управления группами Signed-off-by: Roman Chursanov <[email protected]> * refactor: Большой рефакторинг №2 Удаление неактуальных файлов, обновление докстрингов Signed-off-by: Roman Chursanov <[email protected]> * refactor: Большой рефакторинг №2 Удаление неактуальных файлов, обновление докстрингов Signed-off-by: Roman Chursanov <[email protected]> * fix: Исправлено переоткрытие сессий с базами данных Signed-off-by: Roman Chursanov <[email protected]> * fix: Исправлен возврат к старым результатам поиска Signed-off-by: Roman Chursanov <[email protected]> * feat(groups): Просмотр групповых команд +рефакторинг Signed-off-by: Roman Chursanov <[email protected]> * feat(broadcast): Универсальный диалог рассылок Signed-off-by: Roman Chursanov <[email protected]> * feat(groups): Универсальный диалог групп Signed-off-by: Roman Chursanov <[email protected]> * feat(groups): Универсальный диалог поиска Signed-off-by: Roman Chursanov <[email protected]> * refactor: Перенос стандартных значений фильтров в on_start общих диалогов Signed-off-by: Roman Chursanov <[email protected]> * feat(game): Универсальное игровое меню Signed-off-by: Roman Chursanov <[email protected]> * chore: Возврат части стандартных значений диалогов Signed-off-by: Roman Chursanov <[email protected]> * feat(files): Универсальный диалог управления файлами * feat(files): Загруженные файлы и их история Удаление, редактирование названия, восстановления и загрузка файлов Signed-off-by: Roman Chursanov <[email protected]> * refactor: Рефакторинг ruff Signed-off-by: Roman Chursanov <[email protected]> * refactor: Удалено излишнее логирование Signed-off-by: Roman Chursanov <[email protected]> * feat(files): Загрузка файлов Signed-off-by: Roman Chursanov <[email protected]> * feat(search): Управление уровнями доступа через поиск Signed-off-by: Roman Chursanov <[email protected]> * feat(search): Просмотр графиков и показателей сотрудников через поиск Signed-off-by: Roman Chursanov <[email protected]> * feat(search): Просмотр истории достижений и инвентаря сотрудников Signed-off-by: Roman Chursanov <[email protected]> * feat(schedule): Поддержка нового формата файла старшинств для НЦК Signed-off-by: Roman Chursanov <[email protected]> * feat: Универсальный диалог графиков и показателей Signed-off-by: Roman Chursanov <[email protected]> * fix(achievements): Исправлен список достижений для менеджеров Signed-off-by: Roman Chursanov <[email protected]> * feat(mailing): Отправка уведомлений об активациях специалисту Closes #87 Signed-off-by: Roman Chursanov <[email protected]> * refactor(states): Удалены неиспользуемые состояния Signed-off-by: Roman Chursanov <[email protected]> * feat(group): Просмотр рейтинга группы для руководителей Signed-off-by: Roman Chursanov <[email protected]> * refactor: Удалены лишние вызовы №1 Signed-off-by: Roman Chursanov <[email protected]> * feat(docker): Установка git в контейнер для доступа к stp_database Signed-off-by: Roman Chursanov <[email protected]> * refactor: Удаление uv.lock Signed-off-by: Roman Chursanov <[email protected]> # Conflicts: # uv.lock * feat(database): Использование модуля stp_database Signed-off-by: Roman Chursanov <[email protected]> * feat(files): Возможность использования симлинков папки файлов Signed-off-by: Roman Chursanov <[email protected]> * fix: Исправлен импорт модуля Signed-off-by: Roman Chursanov <[email protected]> * fix: Исправлен импорт модуля Signed-off-by: Roman Chursanov <[email protected]> * feat(database): Поддержка stp_database 1.3.5 Signed-off-by: Roman Chursanov <[email protected]> * fix(rating): Исправлен формат отображения рейтинга для оценки клиента Signed-off-by: Roman Chursanov <[email protected]> * feat(group): Участники группы для руководителей Signed-off-by: Roman Chursanov <[email protected]> * feat(group): График группы для руководителей Signed-off-by: Roman Chursanov <[email protected]> * feat(group): Игровое меню группы для руководителей Signed-off-by: Roman Chursanov <[email protected]> * fix(api): Исправлена инициализация API Signed-off-by: Roman Chursanov <[email protected]> * feat(broadcasts): Поддержка рассылки по уровню доступа Signed-off-by: Roman Chursanov <[email protected]> * feat(docker): Пребилд docker image Signed-off-by: Roman Chursanov <[email protected]> * feat(search): Поддержка возврата в список результатов поиска Signed-off-by: Roman Chursanov <[email protected]> * fix(kpi): Исправлен диалог KPI Signed-off-by: Roman Chursanov <[email protected]> * chore(docker): Замена url репозитория Signed-off-by: Roman Chursanov <[email protected]> * refactor(docker): Оптимизация билда Signed-off-by: Roman Chursanov <[email protected]> * feat(docker): Поддержка директории uploads с загруженными файлами Signed-off-by: Roman Chursanov <[email protected]> * feat: Кнопка-ссылка на саппорт Signed-off-by: Roman Chursanov <[email protected]> * feat(docker): Обновление .dockerignore Signed-off-by: Roman Chursanov <[email protected]> * feat(workflow): Workflow для билда docker image Signed-off-by: Roman Chursanov <[email protected]> * fix(workflow): Исправление workflow Signed-off-by: Roman Chursanov <[email protected]> * feat(docker): Включение uv.lock в билд Docker * feat(docker): Включение uv.lock в билд Docker * refactor(ruff): Досктринги и реформатинг ruff * fix(search): Исправлен возврат в меню поиска из списка руководителей (#104) * chore(deps): bump alembic from 1.16.5 to 1.17.0 (#101) Bumps [alembic](https://github.com/sqlalchemy/alembic) from 1.16.5 to 1.17.0. - [Release notes](https://github.com/sqlalchemy/alembic/releases) - [Changelog](https://github.com/sqlalchemy/alembic/blob/main/CHANGES) - [Commits](https://github.com/sqlalchemy/alembic/commits) --- updated-dependencies: - dependency-name: alembic dependency-version: 1.17.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump sqlalchemy from 2.0.43 to 2.0.44 (#102) Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.43 to 2.0.44. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) --- updated-dependencies: - dependency-name: sqlalchemy dependency-version: 2.0.44 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Roman Chursanov <[email protected]> * chore(deps): bump pandas-stubs from 2.3.2.250827 to 2.3.2.250926 (#86) Bumps [pandas-stubs](https://github.com/pandas-dev/pandas-stubs) from 2.3.2.250827 to 2.3.2.250926. - [Changelog](https://github.com/pandas-dev/pandas-stubs/blob/main/docs/release_procedure.md) - [Commits](pandas-dev/pandas-stubs@v2.3.2.250827...v2.3.2.250926) --- updated-dependencies: - dependency-name: pandas-stubs dependency-version: 2.3.2.250926 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat(docker): Поддержка билдов для бренчей fix/** * feat(docker): Автоматический деплой на сервер Closes #107 * feat(dialogs): Меню рупора для специалистов НЦК Closes #108 * Potential fix for code scanning alert no. 2: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * chore(dialogs): Временно скрыто меню групп * feat(dialogs): Унифицированные окна казино * fix(kpi): Исправлена ошибка просмотра нормативов --------- Signed-off-by: Roman Chursanov <[email protected]> Signed-off-by: Roman Chursanov <[email protected]> Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
1 parent 4ddd251 commit 91381d6

File tree

275 files changed

+15815
-26323
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

275 files changed

+15815
-26323
lines changed

.dockerignore

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,79 @@
1+
Git
2+
.git
3+
.gitignore
4+
.gitattributes
5+
6+
# CI
7+
.codeclimate.yml
8+
.travis.yml
9+
.taskcluster.yml
10+
11+
# Docker
12+
docker-compose.yml
13+
Dockerfile
14+
.docker
15+
.dockerignore
16+
17+
# Byte-compiled / optimized / DLL files
18+
**/__pycache__/
19+
**/*.py[cod]
20+
21+
# C extensions
22+
*.so
23+
24+
# Distribution / packaging
25+
.Python
26+
env/
27+
build/
28+
develop-eggs/
29+
dist/
30+
downloads/
31+
eggs/
32+
lib/
33+
lib64/
34+
parts/
35+
sdist/
36+
var/
37+
*.egg-info/
38+
.installed.cfg
39+
*.egg
40+
41+
# PyInstaller
42+
# Usually these files are written by a python script from a template
43+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
44+
*.manifest
45+
*.spec
46+
47+
# Installer logs
48+
pip-log.txt
49+
pip-delete-this-directory.txt
50+
51+
# Unit test / coverage reports
52+
htmlcov/
53+
.tox/
54+
.coverage
55+
.cache
56+
nosetests.xml
57+
coverage.xml
58+
59+
# Translations
60+
*.mo
61+
*.pot
62+
63+
# Virtual environment
64+
.env
65+
.venv/
166
venv/
2-
.idea/
3-
cache/
4-
README.MD
67+
68+
# PyCharm
69+
.idea
70+
71+
# Python mode for VIM
72+
.ropeproject
73+
**/.ropeproject
74+
75+
# Vim swap files
76+
**/*.swp
77+
78+
# VS Code
79+
.vscode/
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: Docker CI/CD
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- dev
8+
- feature/**
9+
- fix/**
10+
tags:
11+
- 'v*.*.*' # Build on version tags like v1.2.3
12+
13+
env:
14+
REGISTRY: ghcr.io
15+
IMAGE_NAME: stp-team/stpsher
16+
17+
jobs:
18+
build-and-push:
19+
runs-on: ubuntu-latest
20+
permissions:
21+
contents: read
22+
packages: write
23+
id-token: write
24+
25+
steps:
26+
- name: Checkout repository
27+
uses: actions/checkout@v4
28+
29+
- name: Set up Docker Buildx
30+
uses: docker/setup-buildx-action@v3
31+
32+
- name: Log in to GitHub Container Registry
33+
uses: docker/login-action@v3
34+
with:
35+
registry: ${{ env.REGISTRY }}
36+
username: ${{ github.actor }}
37+
password: ${{ secrets.GITHUB_TOKEN }}
38+
39+
- name: Extract Docker metadata
40+
id: meta
41+
uses: docker/metadata-action@v5
42+
with:
43+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
44+
tags: |
45+
type=sha,format=short
46+
type=ref,event=branch
47+
type=semver,pattern={{version}}
48+
type=raw,value=latest,enable=${{ github.ref_name == 'main' }}
49+
50+
- name: Build and push Docker image
51+
uses: docker/build-push-action@v5
52+
with:
53+
context: .
54+
push: true
55+
tags: ${{ steps.meta.outputs.tags }}
56+
labels: ${{ steps.meta.outputs.labels }}
57+
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
58+
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
59+
60+
deploy:
61+
runs-on: ubuntu-latest
62+
permissions:
63+
contents: none
64+
needs: build-and-push
65+
if: github.ref_name == 'main'
66+
steps:
67+
- name: Deploy to server
68+
uses: appleboy/[email protected]
69+
with:
70+
host: ${{ secrets.SERVER_IP }}
71+
username: ${{ secrets.SERVER_USER }}
72+
key: ${{ secrets.SERVER_SSH_KEY }}
73+
script: |
74+
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
75+
docker compose -f /home/stp/projects/stpsher/docker-compose.yml up -d
76+
docker system prune -af
77+

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ venv/
5656
ENV/
5757
env.bak/
5858
venv.bak/
59-
uv.lock
6059

6160
# Pyre type checker
6261
.pyre/

Dockerfile

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
1-
FROM python:3.13-slim
1+
FROM ghcr.io/astral-sh/uv:latest AS uv
2+
FROM python:3.13-slim AS runtime
23

3-
WORKDIR /usr/src/app/stpsher-bot
4+
WORKDIR /app
45

5-
# Установка гита и других зависимостей
6+
# Системные зависимости
67
RUN apt-get update && apt-get install -y --no-install-recommends \
78
git \
8-
&& rm -rf /var/lib/apt/lists/*
9+
&& rm -rf /var/lib/apt/lists/*
910

10-
# Установка uv
11-
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
11+
# Python зависимости
12+
COPY --from=uv /uv /usr/local/bin/uv
13+
COPY pyproject.toml uv.lock* ./
14+
RUN uv sync --frozen --no-dev
15+
COPY . .
1216

13-
# Копирование файлов проекта для лучшего кеширования
14-
COPY pyproject.toml uv.lock* /usr/src/app/stpsher-bot/
17+
# Добавляем виртуальное окружение в PATH
18+
ENV PATH="/app/.venv/bin:$PATH"
1519

16-
# Установка зависимостей Python используя uv (создает .venv)
17-
RUN uv sync --frozen
20+
# Запрет Python записывать файлы .pyc и использовать буферизацию stdout
21+
ENV PYTHONDONTWRITEBYTECODE=1 \
22+
PYTHONUNBUFFERED=1
1823

19-
# Копирование кода проекта
20-
COPY . /usr/src/app/stpsher-bot
21-
22-
# Установка PATH для включения env
23-
ENV PATH="/usr/src/app/stpsher-bot/.venv/bin:$PATH"
24-
25-
CMD ["uv", "run", "python", "main.py"]
24+
CMD ["uv", "run", "python", "bot.py"]

bot.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
BotCommandScopeAllGroupChats,
1212
BotCommandScopeAllPrivateChats,
1313
)
14+
from aiogram_dialog import setup_dialogs
1415
from stp_database import create_engine, create_session_pool
1516

1617
from tgbot.config import Config, load_config
18+
from tgbot.dialogs.menus import common_dialogs_list, dialogs_list
1719
from tgbot.handlers import routers_list
1820
from tgbot.middlewares.ConfigMiddleware import ConfigMiddleware
1921
from tgbot.middlewares.DatabaseMiddleware import DatabaseMiddleware
@@ -28,7 +30,7 @@
2830

2931

3032
async def on_startup():
31-
"""Функция запуска бота"""
33+
"""Функция, активируемая при запуске основного процесса бота."""
3234
pass
3335

3436

@@ -38,12 +40,16 @@ def register_middlewares(
3840
bot: Bot,
3941
main_session_pool=None,
4042
kpi_session_pool=None,
41-
):
42-
"""
43-
Alternative setup with more selective middleware application.
44-
Use this if you want different middleware chains for different event types.
45-
"""
43+
) -> None:
44+
"""Установка middleware для определенных ивентов.
4645
46+
Args:
47+
dp: Диспетчер ивентов
48+
config: Конфигурация
49+
bot: Экземпляр бота
50+
main_session_pool: Сессия с базой данных STP
51+
kpi_session_pool: Сессия с базой данных KPI
52+
"""
4753
config_middleware = ConfigMiddleware(config)
4854
database_middleware = DatabaseMiddleware(
4955
config=config,
@@ -67,16 +73,14 @@ def register_middlewares(
6773
dp.chat_member.outer_middleware(middleware)
6874

6975

70-
def get_storage(config):
71-
"""
72-
Return storage based on the provided configuration.
76+
def get_storage(config) -> RedisStorage | MemoryStorage:
77+
"""Возвращает хранилище исходя из конфигурации.
7378
7479
Args:
75-
config (Config): The configuration object.
80+
config: Объект конфигурации
7681
7782
Returns:
78-
Storage: The storage object based on the configuration.
79-
83+
Хранилище RedisStorage или MemoryStorage
8084
"""
8185
if config.tg_bot.use_redis:
8286
return RedisStorage.from_url(
@@ -87,7 +91,8 @@ def get_storage(config):
8791
return MemoryStorage()
8892

8993

90-
async def main():
94+
async def main() -> None:
95+
"""Основная функция запуска бота."""
9196
setup_logging()
9297

9398
storage = get_storage(bot_config)
@@ -99,7 +104,10 @@ async def main():
99104

100105
# Определение команд для приватных чатов
101106
await bot.set_my_commands(
102-
commands=[BotCommand(command="start", description="Главное меню")],
107+
commands=[
108+
BotCommand(command="start", description="Главное меню"),
109+
BotCommand(command="whois", description="Поиск сотрудников"),
110+
],
103111
scope=BotCommandScopeAllPrivateChats(),
104112
)
105113
await bot.set_my_commands(
@@ -145,21 +153,25 @@ async def main():
145153

146154
dp = Dispatcher(storage=storage)
147155

156+
# Создаем движки для доступа к базам
148157
main_db_engine = create_engine(bot_config.db, db_name=bot_config.db.main_db)
149158
kpi_db_engine = create_engine(bot_config.db, db_name=bot_config.db.kpi_db)
150159

151160
main_db = create_session_pool(main_db_engine)
152161
kpi_db = create_session_pool(kpi_db_engine)
153162

154-
# Store session pools in dispatcher
163+
# Храним сессии в диспетчере
155164
dp["main_db"] = main_db
156165
dp["kpi_db"] = kpi_db
157166

158167
dp.include_routers(*routers_list)
168+
dp.include_routers(*dialogs_list)
169+
dp.include_routers(*common_dialogs_list)
170+
setup_dialogs(dp)
159171

160172
register_middlewares(dp, bot_config, bot, main_db, kpi_db)
161173

162-
# Setup all scheduled jobs using the new scheduler manager
174+
# Запуск планировщика и добавление задач
163175
scheduler_manager = SchedulerManager()
164176
scheduler_manager.setup_jobs(main_db, bot, kpi_db)
165177
scheduler_manager.start()

docker-compose.yml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
services:
22
bot:
3-
image: "stpsher-bot"
3+
image: "ghcr.io/stp-team/stpsher:latest"
44
stop_signal: SIGINT
5-
build:
6-
context: .
7-
working_dir: "/usr/src/app/stpsher-bot"
8-
volumes:
9-
- .:/usr/src/app/stpsher-bot
10-
command: uv run bot.py
115
restart: always
126
env_file:
137
- ".env"
8+
volumes:
9+
- ./uploads:/app/uploads
1410

1511
logging:
1612
driver: "json-file"
@@ -27,9 +23,14 @@ services:
2723
command: redis-server --port $REDIS_PORT --save 20 1 --loglevel warning --requirepass $REDIS_PASSWORD
2824
env_file:
2925
- ".env"
26+
ports:
27+
- "6379:6379"
3028
volumes:
3129
- cache:/data
3230

31+
networks:
32+
- stp_bots
33+
3334
volumes:
3435
cache: { }
3536

infrastructure/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Интеграция внешних API."""

infrastructure/api/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Интеграция API производственного календаря."""

0 commit comments

Comments
 (0)