Skip to content

Commit

Permalink
Merge pull request #55 from Studio-Yandex-Practicum-Hackathons/docker…
Browse files Browse the repository at this point in the history
…_png_menu

Docker png menu
  • Loading branch information
luydmila-davletova committed Nov 28, 2023
2 parents e6ab21c + 3caaf2d commit b327a4c
Show file tree
Hide file tree
Showing 38 changed files with 480 additions and 503 deletions.
130 changes: 66 additions & 64 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,105 +4,107 @@

## Технологии
- Python 3.11
- aiogram 3.1.1
- Django 4.2.7
- Django REST Framework 3.14.0
- Aiogram 3.1.1
- PostgreSQL 13.0
- Nginx 1.21.3
- Docker
- Docker-compose
- Docker Hub
- SunriseSunset.io API

# Установка
## Копирование репозитория
Клонируем репозиторий
Клонируем репозиторий и переходим в директорию infra:
```
~ git clone [email protected]:Studio-Yandex-Practicum-Hackathons/cafe_azu_bot_2.git
~ cd ./cafe_azu_bot_2/infra/
```
Требуется изменить server_name и listen в ./infra/nginx/default.conf, ports в docker-compose.yml

## Виртуальное окружение
Переходим в клонированный репозиторий
## Подготовка боевого сервера:
1. Перейдите на боевой сервер:
```
~ cd {путь до папки с клонированным репозиторем}
~ cd cafe_azu_bot_2
ssh username@server_address
```
Устанавливаем и активируем виртуальное окружение
2. Обновите индекс пакетов APT:
```
~ py -3.11 -m venv venv
~ . venv/Scripts/activate
sudo apt update
```
Устанавливаем требуемые зависимости:
и обновите установленные в системе пакеты и установите обновления безопасности:
```
~ pip install -r requirements.txt
sudo apt upgrade -y
```

## База данных
В azu_bot_django/azu_bot_django/settings.py выбрать нужную базу данных (sqlite3 или postgresql)
### env
Выполнять только если ваша база данных postgresql, иначе пропустить этот шаг.

Создать файл .env со следующими строками для базы данных:
- DATABASE_NAME = <Имя для подключения к базе данных>
- DATABASE_USERNAME = <Имя пользователя для подключения к базе данных>
- DATABASE_PASSWORD = <Пароль для подключения к базе данных>
- DATABASE_HOST = <Адрес для подключения к базе данных>
- DATABASE_PORT = <Порт для подключения к базе данных>

### Миграция базы данных
Переходим в раздел с базой данных:
Создайте папку `nginx`:
```
~ cd azu_bot_django
mkdir nginx
```
Скопируйте файлы docker-compose.yaml, nginx/default.conf из вашего проекта на сервер в home/<ваш_username>/docker-compose.yaml, home/<ваш_username>/nginx/default.conf соответственно:
```
Создаем миграцию для базы данных:
scp docker-compose.yaml <username>@<host>/home/<username>/docker-compose.yaml
scp default.conf <username>@<host>/home/<username>/nginx/default.conf
```
~ py manage.py makemigrations cafe tables menu admin_users reservation
Установите Docker и Docker-compose:
```
И применяем её:
sudo apt install docker.io
```
~ py manage.py migrate
```
Создаем суперпользователя:
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
```
~ py manage.py createsuperuser
```
Вводим поочередно Имя пользователя, почту и пароль.
После чего запускаем базу данных:
sudo chmod +x /usr/local/bin/docker-compose
```
~ uvicorn azu_bot_django.asgi:application
Проверьте правильность установки Docker-compose:
```

### Наполнение базы данных
Доступ к сервису становится доступен по [адресу](http://127.0.0.1:8000/admin/)

По указаному адресу нужно создать хотя бы по одному экземпляру кафе, стола, блюда, сета.

Так же можно наполнить базу данных готовыми сетами, блюдами, кафе и столам:

sudo docker-compose --version
```
~ py manage.py load_data
На боевом сервере создайте файл .env:
```

Создать пользователя-бота и по [адресу](http://127.0.0.1:8000/admin/authtoken/tokenproxy/) нужно создать токен для бота.

## Телеграмм-бот
### env
В уже существующем файле .env добавить строки:
touch .env
```
и заполните переменные окружения
```
nano .env
- TOKEN = <Токен бота, можно получить у BotFather>
- ADMIN_ID = <ID телеграмм-аккаунта админа>
- PROVIDER_TOKEN = <Токен платежной системы>
- DJANGO_TOKEN = <Токен для бота, который был создан в разделе базы данных>
- PORT = <Порт подключения Django>
- SECRET_KEY=<SECRET_KEY>
### Запуск
Для полного запуска телеграмм-бота с базой данных нужно:
- Выполнить все вышеуказанные действия по установке.
- Перейти в папку с базой данных и запустить её:
- POSTGRES_DB = postgres
- POSTGRES_USER = postgres
- POSTGRES_PASSWORD = postgres
- POSTGRES_HOST = db
- POSTGRES_PORT = 5432
- WEB_HOST = <ip сервера>
- WEB_PORT = <Порт сервера>
- WEB_PROTOKOL = <Протокол сервера>
```

## Развертывание проекта с помощью Docker:
Разворачиваем контейнеры в фоновом режиме из папки infra:
```
sudo docker compose up -d
```
При первом запуске выполняем миграции:
```
~ cd azu_bot_django
~ python manage.py collectstatic
~ uvicorn azu_bot_django.asgi:application
sudo docker compose exec backend python manage.py migrate
```
- Выйти из папки с базой данных, перейти в папку бота, запустить бота:
И собираем статику:
```
~ cd azu_bot_aiogram
~ py main.py
sudo docker compose exec backend python manage.py collectstatic --no-input
```
Телеграмм-бот готов к работе.
Создаем суперпользователя:
```
sudo docker compose exec backend python manage.py createsuperuser
```
Загружаем данные из csv-таблиц в базу данных:
```
sudo docker compose exec backend python manage.py load_data
```

# Адресные пути
- [Документация к API базе данных](http://127.0.0.1:8000/redoc)
- [Админ-панель базы данных](http://127.0.0.1:8000/admin)
Expand Down
9 changes: 9 additions & 0 deletions azu_bot_aiogram/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM python:3.11-slim

WORKDIR /app

COPY ./ ./

RUN pip3 install -r requirements.txt --no-cache-dir

CMD ["python", "main.py"]
2 changes: 1 addition & 1 deletion azu_bot_aiogram/filters/is_correct_person_amount.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
class IsPersonAmount(BaseFilter):
async def __call__(self, message: Message) -> bool:
"""Проверка корректности ввода количества человек."""
pattern = r'^(0?[1-9]|[12][0-9]|3[01])$'
pattern = r'^(0?[1-9]|[123][0-9]|4[0])$'
if re.match(pattern, message.text):
return {'amount': message.text}
else:
Expand Down
38 changes: 14 additions & 24 deletions azu_bot_aiogram/handlers/api.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,33 @@
import aiohttp
from settings import django_token, settings


async def get_session():
session = aiohttp.ClientSession(
headers={"Authorization": f'Token {django_token}'}
)
return session
async def get_cafe(bot=None):
async with aiohttp.ClientSession() as session:
async with session.get(
'http://backend:8000/cafes/'
) as response:
return await response.json()


async def get_cafe(bot=None):
mysession = await get_session()
async with mysession as session:
async def get_cafe_admins(cafe):
async with aiohttp.ClientSession() as session:
async with session.get(
'http://127.0.0.1:8000/cafes/'
f'http://backend:8000/cafes/{cafe}/admins/'
) as response:
response = await response.json()
if not isinstance(response, list):
await bot.send_message(
chat_id=settings.bots.admin_id,
text='Нет связи с базой данных!'
'Проверьте валидность django token!')
return None
return response
return await response.json()


async def post_quantity(cafe, data):
mysession = await get_session()
async with mysession as session:
async with aiohttp.ClientSession() as session:
async with session.post(
f'http://127.0.0.1:8000/cafes/{cafe}/quantity/', json=data
f'http://backend:8000/cafes/{cafe}/quantity/', json=data
) as response:
return await response.json()


async def post_reservation(cafe_id, data):
mysession = await get_session()
async with mysession as session:
async with aiohttp.ClientSession() as session:
async with session.post(
f'http://127.0.0.1:8000/cafes/{cafe_id}/reservations/', json=data
f'http://backend:8000/cafes/{cafe_id}/reservations/', json=data
) as response:
return await response.json()
53 changes: 49 additions & 4 deletions azu_bot_aiogram/handlers/appsched.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from aiogram.fsm.context import FSMContext
from aiogram.types import Message
from apscheduler.schedulers.asyncio import AsyncIOScheduler

from handlers.api import get_cafe
from handlers.get_sunset import get_sunset_from_api
from keyboards.reply_keyboards import reminder_kbd
from utils.states import StepsForm
Expand All @@ -13,7 +15,7 @@ async def get_reminder_time(message: Message, bot: Bot, state: FSMContext):
"""Выбор клиентом времени напоминания об ифтаре."""
await message.answer(
'Уведомляем Вас, что отмена брони и возврат средств '
'возможны не менее чем за 3 часа до начала ифтара.\n'
'возможны не менее чем за 2 часа до начала ифтара.\n'
'За какой период времени до начала ифтара отправить Вам напоминание?',
reply_markup=reminder_kbd())
await state.set_state(StepsForm.REMINDER_STATE)
Expand All @@ -40,21 +42,35 @@ async def three_hours_before_iftar(
'bot': bot, 'chat_id': message.from_user.id, 'state': state
}
)
await message.answer(
'За 3 часа до ифтара в выбранный день мы отправим напоминание.\n'
'С нетерпением ждем встречи с Вами!\n'
'Ваше кафе "АЗУ"'
)


async def send_reminder_3_hours(bot: Bot, chat_id: int, state: FSMContext):
"""Напоминание о предстоящем ифтаре за 3 часа до начала."""
cafes = await get_cafe(bot)
context_data = await state.get_data()
name = context_data.get('name')
address = context_data.get('address')
date = context_data.get('date')
person_amount = context_data.get('person_amount')
cafe_number = ''
for cafe in cafes:
if cafe['address'] == address:
break
cafe_number = cafe['number']
text = (
f'Ассэламуалейкум, {name}!\n'
f'Напоминаем Вам, что {date} вы забронировали стол '
f'на {person_amount} человека в кафе A Z U.\n'
'До начала ифтара осталось менее 3 часов.\n'
f'Мы ждем Вас по адресу: г. Казань, {address}.')
f'Мы ждем Вас по адресу: г. Казань, {address}.\n'
'Для отмены брони пожалуйста свяжитесь с нами '
f'по телефону {cafe_number}'
)
await bot.send_message(chat_id=chat_id, text=text)


Expand All @@ -79,24 +95,53 @@ async def one_day_before_iftar(
'bot': bot, 'chat_id': message.from_user.id, 'state': state
}
)
await message.answer(
'За 24 часа до начала ифтара мы отправим напоминание.\n'
'С нетерпением ждем встречи с Вами!\n'
'Ваше кафе "АЗУ"'
)


async def send_reminder_1_day(bot: Bot, chat_id: int, state: FSMContext):
"""Напоминание о предстоящем ифтаре за 1 сутки до начала."""
cafes = await get_cafe(bot)
context_data = await state.get_data()
name = context_data.get('name')
address = context_data.get('address')
date = context_data.get('date')
person_amount = context_data.get('person_amount')
cafe_number = ''
for cafe in cafes:
if cafe['address'] == address:
break
cafe_number = cafe['number']
text = (
f'Ассэламуалейкум, {name}!\n'
f'Напоминаем Вам, что {date} вы забронировали стол '
f'на {person_amount} человека в кафе A Z U.\n'
'До начала ифтара осталось менее 24 часов.\n'
f'Мы ждем Вас по адресу: г. Казань, {address}.')
f'Мы ждем Вас по адресу: г. Казань, {address}.'
'Для отмены брони пожалуйста свяжитесь с нами '
f'по телефону {cafe_number}'
)
await bot.send_message(chat_id=chat_id, text=text)


async def no_reminder(message: Message, bot: Bot, state: FSMContext):
"""Обработчик отсутствия необходимости напоминания."""
pass
cafes = await get_cafe(bot)
context_data = await state.get_data()
address = context_data.get('address')
cafe_number = ''
for cafe in cafes:
if cafe['address'] == address:
break
cafe_number = cafe['number']
await message.answer(
'Вы отказались от напоминания об ифтаре.\n'
'Если, к нашему глубокому сожалению, Вы не сможете '
'посетить ифтар, пожалуйста свяжитесь с нами по телефону '
f'{cafe_number} для отмены заказа.\n'
'С нетерпением ждем встречи с Вами!\n'
'Ваше кафе "АЗУ"'
)
Loading

0 comments on commit b327a4c

Please sign in to comment.