chore: Global project refactoring

This commit is contained in:
ITQ
2024-03-23 10:47:39 +03:00
parent 1802ce81b0
commit 7b28635f09
21 changed files with 143 additions and 116 deletions
-2
View File
@@ -1,9 +1,7 @@
# Files
README.md
task.md
check.sh
.flake8
template.env
# Folders
img/
-3
View File
@@ -2,11 +2,8 @@ FROM python:3.12-slim
WORKDIR /app
# Copy requirements file
COPY requirements/prod.txt .
# Install Python dependencies
RUN pip install --no-cache-dir -r prod.txt
# Copy the rest of the application files
COPY . .
+3 -4
View File
@@ -38,10 +38,9 @@ async def main() -> None:
profile_command.router,
create_travel_command.router,
travels_command.router,
menu.router, # type: ignore
profile.router, # type: ignore
travels.router, # type: ignore
menu.router,
profile.router,
travels.router,
)
await bot.delete_webhook(drop_pending_updates=True)
+17 -17
View File
@@ -1,11 +1,10 @@
# type: ignore
__all__ = ()
__all__ = ("router",)
from aiogram import F, Router
from aiogram.exceptions import TelegramBadRequest
from aiogram.filters import StateFilter
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery
from aiogram.types import CallbackQuery, Message
from app import messages
from app.config import Config
@@ -20,24 +19,18 @@ router = Router(name="menu_callback")
@router.callback_query(
F.data == "menu_profile", RegisteredCallback(), StateFilter(None),
F.data == "menu_profile",
RegisteredCallback(),
StateFilter(None),
)
async def profile_callback(callback: CallbackQuery) -> None:
if callback.data is None or callback.message is None:
if callback.data is None or not isinstance(callback.message, Message):
return
user = User().get_user_by_telegram_id(callback.from_user.id)
await callback.message.answer(
messages.PROFILE.format(
username=user.username,
age=user.age,
bio=user.bio if user.bio else messages.NOT_SET,
sex=user.sex.capitalize(),
country=user.country,
city=user.city,
date_joined=user.get_human_readable_datejoined(),
),
user.get_profile_text(),
reply_markup=get(),
)
await callback.answer()
@@ -49,13 +42,15 @@ async def profile_callback(callback: CallbackQuery) -> None:
@router.callback_query(
F.data == "menu_create_travel", RegisteredCallback(), StateFilter(None),
F.data == "menu_create_travel",
RegisteredCallback(),
StateFilter(None),
)
async def create_travel_callback(
callback: CallbackQuery,
state: FSMContext,
) -> None:
if callback.data is None or callback.message is None:
if callback.data is None or not isinstance(callback.message, Message):
return
await callback.message.answer(
@@ -75,11 +70,16 @@ async def create_travel_callback(
@router.callback_query(
F.data == "menu_travels", RegisteredCallback(), StateFilter(None),
F.data == "menu_travels",
RegisteredCallback(),
StateFilter(None),
)
async def travels_callback(
callback: CallbackQuery,
) -> None:
if not isinstance(callback.message, Message):
return
page = 0
user = User().get_user_by_telegram_id(callback.from_user.id)
+14 -12
View File
@@ -1,5 +1,4 @@
# type: ignore
__all__ = ()
__all__ = ("router",)
from aiogram import F, Router
from aiogram.exceptions import TelegramBadRequest
@@ -31,7 +30,11 @@ async def profile_change_callback(
callback: CallbackQuery,
state: FSMContext,
) -> None:
if callback.data is None or callback.message is None:
if (
callback.data is None
or callback.message is None
or not isinstance(callback.message, Message)
):
return
column = callback.data.replace("profile_change_", "")
@@ -70,6 +73,13 @@ async def profile_change_callback(
@router.message(UserAlteringState.value, F.text, Registered())
async def profile_change_entered(message: Message, state: FSMContext) -> None:
if (
message.text is None
or message.from_user is None
or message.bot is None
):
return
column = (await state.get_data()).get("column")
value = message.text.strip()
@@ -205,15 +215,7 @@ async def profile_change_entered(message: Message, state: FSMContext) -> None:
try:
await message.bot.edit_message_text(
messages.PROFILE.format(
username=user.username,
age=user.age,
bio=user.bio if user.bio else messages.NOT_SET,
sex=user.sex.capitalize(),
country=user.country,
city=user.city,
date_joined=user.get_human_readable_datejoined(),
),
user.get_profile_text(),
message.chat.id,
state_data["profile_message_id"],
reply_markup=get(),
+8 -4
View File
@@ -1,10 +1,9 @@
# type: ignore
__all__ = ()
__all__ = ("router",)
from aiogram import F, Router
from aiogram.exceptions import TelegramBadRequest
from aiogram.filters import StateFilter
from aiogram.types import CallbackQuery
from aiogram.types import CallbackQuery, Message
from app import messages
from app.config import Config
@@ -17,9 +16,14 @@ router = Router(name="menu_callback")
@router.callback_query(
F.data.startswith("travels_page"), RegisteredCallback(), StateFilter(None),
F.data.startswith("travels_page"),
RegisteredCallback(),
StateFilter(None),
)
async def travels_callback(callback: CallbackQuery) -> None:
if callback.data is None or not isinstance(callback.message, Message):
return
page = int(callback.data.replace("travels_page_", ""))
user = User().get_user_by_telegram_id(callback.from_user.id)
+1 -1
View File
@@ -1,4 +1,4 @@
__all__ = ()
__all__ = ("router",)
from aiogram import F, Router
from aiogram.filters import Command, StateFilter
+1 -1
View File
@@ -1,4 +1,4 @@
__all__ = ()
__all__ = ("router",)
from aiogram import Router
from aiogram.filters import Command, StateFilter
+1 -1
View File
@@ -1,4 +1,4 @@
__all__ = ()
__all__ = ("router",)
from aiogram import Router
from aiogram.filters import Command, StateFilter
+2 -11
View File
@@ -1,10 +1,9 @@
__all__ = ()
__all__ = ("router",)
from aiogram import Router
from aiogram.filters import Command, StateFilter
from aiogram.types import Message
from app import messages
from app.filters.user import Registered
from app.keyboards.profile import get
from app.models.user import User
@@ -21,14 +20,6 @@ async def command_profile_handler(message: Message) -> None:
user = User().get_user_by_telegram_id(message.from_user.id)
await message.answer(
messages.PROFILE.format(
username=user.username,
age=user.age,
bio=user.bio if user.bio else messages.NOT_SET,
sex=user.sex.capitalize(),
country=user.country,
city=user.city,
date_joined=user.get_human_readable_datejoined(),
),
user.get_profile_text(),
reply_markup=get(),
)
+1 -1
View File
@@ -1,4 +1,4 @@
__all__ = ()
__all__ = ("router",)
from aiogram import F, Router
from aiogram.filters import CommandStart, StateFilter
+1 -1
View File
@@ -1,4 +1,4 @@
__all__ = ()
__all__ = ("router",)
from aiogram import Router
from aiogram.filters import Command, StateFilter
+36 -32
View File
@@ -1,4 +1,4 @@
__all__ = ("sex_keyboard",)
__all__ = ("sex_keyboard", "travels_keyboard")
from aiogram.types import InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder, ReplyKeyboardBuilder
@@ -10,9 +10,9 @@ def sex_keyboard(choices: str | list):
builder = ReplyKeyboardBuilder()
if isinstance(choices, str):
text = [choices]
choices = [choices]
[builder.button(text=txt) for txt in text]
[builder.button(text=choice) for choice in choices]
return builder.as_markup(resize_keyboard=True)
@@ -36,41 +36,45 @@ def travels_keyboard(travels: list, page: int, pages: int):
builder.row(*rows, width=2)
if pages > 1:
navigation_row = []
if page > 0:
navigation_row.append(
InlineKeyboardButton(
text="⬅️", callback_data=f"travels_page_{page - 1}",
),
)
else:
navigation_row.append(
InlineKeyboardButton(
text=" ", callback_data="pass",
),
)
navigation_row = []
if page > 0:
navigation_row.append(
InlineKeyboardButton(
text=f"{page + 1}/{pages}", callback_data="pass",
text="⬅️",
callback_data=f"travels_page_{page - 1}",
),
)
else:
navigation_row.append(
InlineKeyboardButton(
text=" ",
callback_data="pass",
),
)
if page < pages - 1:
navigation_row.append(
InlineKeyboardButton(
text="➡️", callback_data=f"travels_page_{page + 1}",
),
)
else:
navigation_row.append(
InlineKeyboardButton(
text=" ", callback_data="pass",
),
)
navigation_row.append(
InlineKeyboardButton(
text=f"{page + 1}/{pages}",
callback_data="pass",
),
)
builder.row(*navigation_row)
if page < pages - 1:
navigation_row.append(
InlineKeyboardButton(
text="➡️",
callback_data=f"travels_page_{page + 1}",
),
)
else:
navigation_row.append(
InlineKeyboardButton(
text=" ",
callback_data="pass",
),
)
builder.row(*navigation_row)
return builder.as_markup()
+7 -5
View File
@@ -4,14 +4,16 @@ MENU = "<b>Menu:</b>"
TRAVELS = "📃 <b>Travels:</b>"
NO_TRAVELS = "No travels yet. You can create one with /create_travel command."
CREATE_TRAVEL = "🧳 Let's create new travel!\n<i>Enter /cancel to cancel creating.</i>"
INPUT_TRAVEL_TITLE = "Enter travel title:\n<i>Maximum length: 30 characters</i>"
CREATE_TRAVEL = (
"🧳 Let's create new travel!\n<i>Enter /cancel to cancel creating.</i>"
)
INPUT_TRAVEL_TITLE = (
"Enter travel title:\n<i>Maximum length: 30 characters</i>"
)
INPUT_TRAVEL_CALLBACK = (
"All right, travel <b>{key}</b> is set to: <b>{value}</b>"
)
INPUT_TRAVEL_DESCRIPTION = (
"Enter travel description (enter /skip if you want to skip this step):\n<i>Maximum length: 100 characters</i>"
)
INPUT_TRAVEL_DESCRIPTION = "Enter travel description (enter /skip if you want to skip this step):\n<i>Maximum length: 100 characters</i>"
INPUT_TRAVEL_DESCRIPTION_SKIPPED = "Sure. You can always fill it later."
TRAVEL_CREATED = "Travel <b>{title}</b> successfully created! You can now view and edit it in the travels list (/travels command)."
ACTION_CANCELED = "❌ Action canceled"
+14 -3
View File
@@ -5,7 +5,7 @@ import re
import sqlalchemy as sa
from sqlalchemy.orm import relationship, validates
from app import session
from app import messages, session
from app.models import Base
from app.utils import geo
from app.utils.db import utcnow
@@ -97,12 +97,23 @@ class User(Base):
return normalized_value
def get_user_travels(self):
def get_user_travels(self) -> list:
return self.owned_travels + self.travels
def get_human_readable_datejoined(self):
def get_human_readable_datejoined(self) -> str:
return self.date_joined.strftime("%Y-%m-%d %H:%M:%S")
def get_profile_text(self) -> str:
return messages.PROFILE.format(
username=self.username,
age=self.age,
bio=self.bio if self.bio else messages.NOT_SET,
sex=self.sex.capitalize(),
country=self.country,
city=self.city,
date_joined=self.get_human_readable_datejoined(),
)
@classmethod
def get_user_queryset_by_telegram_id(cls, telegram_id):
return session.query(cls).filter(cls.telegram_id == telegram_id)
View File
+1 -1
View File
@@ -6,7 +6,7 @@ NC='\033[0m'
sort-requirements requirements/dev.txt
sort-requirements requirements/prod.txt
sort-requirements requirements/test.txt
sort-requirements requirements/lints.txt
sort-requirements requirements/lint.txt
printf "${GREEN}Requirements sorted${NC}\n"
black .
+17 -14
View File
@@ -3,7 +3,7 @@ version: "3"
services:
postgres:
image: postgres:latest
image: postgres:16.2-alpine
container_name: postgres
healthcheck:
test: pg_isready -U postgres -h localhost
@@ -11,21 +11,21 @@ services:
timeout: 5s
retries: 10
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: wTAb5KoZ4dBtscg
POSTGRES_DB: ${POSTGRES_DB:-postgres}
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
ports:
- "5432:5432"
- "${POSTGRES_PORT:-5432}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:latest
image: redis:7.2-alpine
container_name: redis
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
ports:
- "6379:6379"
- "${REDIS_PORT:-6379}:6379"
volumes:
- redis_data:/data
@@ -38,20 +38,23 @@ services:
redis:
condition: service_healthy
environment:
BOT_TOKEN: 6943803094:AAEHG-vOP2pNEuxb9rDIhisiQuGLuBIjx1Q
REDIS_URL: redis://redis:6379/
SQLALCHEMY_DATABASE_URI: postgresql://postgres:wTAb5KoZ4dBtscg@postgres:5432/postgres
BOT_TOKEN: ${BOT_TOKEN:-6943803094:AAEHG-vOP2pNEuxb9rDIhisiQuGLuBIjx1Q}
REDIS_URL: redis://redis:${REDIS_PORT:-6379}/
SQLALCHEMY_DATABASE_URI: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
entrypoint: ["bash", "-c"]
command: ["alembic -c app/alembic.ini upgrade head && python -m app"]
pgadmin:
image: dpage/pgadmin4
image: dpage/pgadmin4:8.4
container_name: pgadmin
depends_on:
postgres:
condition: service_healthy
environment:
PGADMIN_DEFAULT_EMAIL: admin@mail.com
PGADMIN_DEFAULT_PASSWORD: admin
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL:-admin@mail.com}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD:-admin}
ports:
- "5050:80"
- "${PGADMIN_PORT:-5050}:80"
restart: always
volumes:
- pgadmin_data:/var/lib/pgadmin
+1 -1
View File
@@ -4,4 +4,4 @@ sort-requirements
-r prod.txt
-r test.txt
-r lints.txt
-r lint.txt
+18 -2
View File
@@ -1,2 +1,18 @@
BOT_TOKEN = <your_bot_token>
SQLALCHEMY_DATABASE_URI = <database_uri>
# For app
BOT_TOKEN = <your_bot_token> # default: 6943803094:AAEHG-vOP2pNEuxb9rDIhisiQuGLuBIjx1Q
SQLALCHEMY_DATABASE_URI = <database_uri> # no need to specify if docker is used
REDIS_URL = <redis_url> # no need to specify if docker is used
# For docker(remove if you want to keep defaults)
POSTGRES_PORT = <port_to_be_forwared> # default: 5432
POSTGRES_DB = <db_name> # default: postgres
POSTGRES_USER = <postgres_user> # default: postgres
POSTGRES_PASSWORD = <password> # default: postgres
REDIS_PORT = <port_to_be_forwared> # default: 6379
PGADMIN_PORT = <port_to_be_forwared> # default: 5050
PGADMIN_EMAIL = <email> # default: admin@mail.com
PGADMIN_PASSWORD <password> # default: admin