You've already forked Travel-Agent
feat: Added redis as storage for states, improvements in docker compose, added /cancel command, small improvements
This commit is contained in:
@@ -10,8 +10,3 @@ RUN pip install --no-cache-dir -r prod.txt
|
|||||||
|
|
||||||
# Copy the rest of the application files
|
# Copy the rest of the application files
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Apply migrations
|
|
||||||
# RUN alembic -c app/alembic.ini upgrade head
|
|
||||||
|
|
||||||
CMD ["python", "-m", "app"]
|
|
||||||
|
|||||||
+18
-16
@@ -4,6 +4,7 @@ from typing import Optional
|
|||||||
|
|
||||||
from aiogram import Bot, Dispatcher
|
from aiogram import Bot, Dispatcher
|
||||||
from aiogram.enums import ParseMode
|
from aiogram.enums import ParseMode
|
||||||
|
from aiogram.fsm.storage.redis import RedisStorage
|
||||||
|
|
||||||
from app.callbacks import profile
|
from app.callbacks import profile
|
||||||
from app.config import Config
|
from app.config import Config
|
||||||
@@ -12,22 +13,23 @@ from app.middlewares.throttling import ThrottlingMiddleware
|
|||||||
|
|
||||||
|
|
||||||
async def main() -> None:
|
async def main() -> None:
|
||||||
dp = Dispatcher()
|
|
||||||
|
|
||||||
bot_token: Optional[str] = Config.BOT_TOKEN
|
bot_token: Optional[str] = Config.BOT_TOKEN
|
||||||
if bot_token is not None:
|
|
||||||
bot = Bot(bot_token, parse_mode=ParseMode.HTML)
|
|
||||||
|
|
||||||
dp.message.middleware(ThrottlingMiddleware(0.5))
|
if bot_token is None:
|
||||||
# type: ignore
|
|
||||||
dp.include_routers(
|
|
||||||
start_command.router,
|
|
||||||
profile_command.router,
|
|
||||||
profile.router,
|
|
||||||
help_command.router,
|
|
||||||
)
|
|
||||||
|
|
||||||
await bot.delete_webhook(drop_pending_updates=True)
|
|
||||||
await dp.start_polling(bot)
|
|
||||||
else:
|
|
||||||
exit("BOT_TOKEN is not set")
|
exit("BOT_TOKEN is not set")
|
||||||
|
|
||||||
|
storage = RedisStorage.from_url(Config.REDIS_URL)
|
||||||
|
dp = Dispatcher(storage=storage)
|
||||||
|
bot = Bot(bot_token, parse_mode=ParseMode.HTML)
|
||||||
|
|
||||||
|
dp.message.middleware(ThrottlingMiddleware(0.5))
|
||||||
|
# type: ignore
|
||||||
|
dp.include_routers(
|
||||||
|
start_command.router,
|
||||||
|
profile_command.router,
|
||||||
|
profile.router, # type: ignore
|
||||||
|
help_command.router,
|
||||||
|
)
|
||||||
|
|
||||||
|
await bot.delete_webhook(drop_pending_updates=True)
|
||||||
|
await dp.start_polling(bot)
|
||||||
|
|||||||
+98
-24
@@ -33,23 +33,31 @@ async def profile_change_callback(
|
|||||||
column = callback.data.replace("profile_change_", "")
|
column = callback.data.replace("profile_change_", "")
|
||||||
|
|
||||||
if column == "username":
|
if column == "username":
|
||||||
message = await callback.message.answer(messages.EDIT_USERNAME)
|
message = await callback.message.answer(
|
||||||
|
f"{messages.EDIT_USERNAME}\n{messages.CANCEL_CHANGE}",
|
||||||
|
)
|
||||||
elif column == "age":
|
elif column == "age":
|
||||||
message = await callback.message.answer(messages.INPUT_AGE)
|
message = await callback.message.answer(
|
||||||
|
f"{messages.INPUT_AGE}\n{messages.CANCEL_CHANGE}",
|
||||||
|
)
|
||||||
elif column == "bio":
|
elif column == "bio":
|
||||||
message = await callback.message.answer(messages.EDIT_BIO)
|
message = await callback.message.answer(
|
||||||
|
f"{messages.EDIT_BIO}\n{messages.CANCEL_CHANGE}",
|
||||||
|
)
|
||||||
elif column == "sex":
|
elif column == "sex":
|
||||||
message = await callback.message.answer(
|
message = await callback.message.answer(
|
||||||
messages.INPUT_SEX,
|
f"{messages.INPUT_SEX}\n{messages.CANCEL_CHANGE}",
|
||||||
reply_markup=profile(["Male", "Female"]),
|
reply_markup=profile(["Male", "Female"]),
|
||||||
)
|
)
|
||||||
elif column == "location":
|
elif column == "location":
|
||||||
message = await callback.message.answer(messages.INPUT_LOCATION)
|
message = await callback.message.answer(
|
||||||
|
f"{messages.INPUT_LOCATION}\n{messages.CANCEL_CHANGE}",
|
||||||
|
)
|
||||||
|
|
||||||
await state.update_data(
|
await state.update_data(
|
||||||
column=column,
|
column=column,
|
||||||
message_id=callback.message.message_id,
|
message_id=callback.message.message_id,
|
||||||
input_message=message,
|
input_message_id=message.message_id,
|
||||||
)
|
)
|
||||||
await state.set_state(UserAltering.value)
|
await state.set_state(UserAltering.value)
|
||||||
|
|
||||||
@@ -61,6 +69,23 @@ async def profile_change_entered(message: Message, state: FSMContext) -> None:
|
|||||||
column = (await state.get_data()).get("column")
|
column = (await state.get_data()).get("column")
|
||||||
value = message.text.strip()
|
value = message.text.strip()
|
||||||
|
|
||||||
|
if value == "/cancel":
|
||||||
|
await message.answer(
|
||||||
|
messages.CHANGE_CANCELED,
|
||||||
|
reply_markup=ReplyKeyboardRemove(),
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.update_data(successfully=True)
|
||||||
|
await message.delete()
|
||||||
|
await delete_message_from_state(
|
||||||
|
state,
|
||||||
|
message.chat.id,
|
||||||
|
message.bot,
|
||||||
|
)
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
if column == "username":
|
if column == "username":
|
||||||
try:
|
try:
|
||||||
validated_value = User().validate_username(
|
validated_value = User().validate_username(
|
||||||
@@ -69,10 +94,16 @@ async def profile_change_entered(message: Message, state: FSMContext) -> None:
|
|||||||
)
|
)
|
||||||
except AssertionError as e:
|
except AssertionError as e:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(
|
||||||
|
state,
|
||||||
|
message.chat.id,
|
||||||
|
message.bot,
|
||||||
|
)
|
||||||
|
|
||||||
error_message = await message.answer(str(e))
|
error_message = await message.answer(str(e))
|
||||||
await state.update_data(previous_message=error_message)
|
await state.update_data(
|
||||||
|
previous_message_id=error_message.message_id,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -82,10 +113,16 @@ async def profile_change_entered(message: Message, state: FSMContext) -> None:
|
|||||||
validated_age = User().validate_age(key="age", value=value)
|
validated_age = User().validate_age(key="age", value=value)
|
||||||
except AssertionError as e:
|
except AssertionError as e:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(
|
||||||
|
state,
|
||||||
|
message.chat.id,
|
||||||
|
message.bot,
|
||||||
|
)
|
||||||
|
|
||||||
error_message = await message.answer(str(e))
|
error_message = await message.answer(str(e))
|
||||||
await state.update_data(previous_message=error_message)
|
await state.update_data(
|
||||||
|
previous_message_id=error_message.message_id,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -93,16 +130,26 @@ async def profile_change_entered(message: Message, state: FSMContext) -> None:
|
|||||||
elif column == "bio":
|
elif column == "bio":
|
||||||
if value == "/skip":
|
if value == "/skip":
|
||||||
await state.update_data(value=None, successfully=True)
|
await state.update_data(value=None, successfully=True)
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(
|
||||||
|
state,
|
||||||
|
message.chat.id,
|
||||||
|
message.bot,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
validated_bio = User().validate_bio(key="bio", value=value)
|
validated_bio = User().validate_bio(key="bio", value=value)
|
||||||
except AssertionError as e:
|
except AssertionError as e:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(
|
||||||
|
state,
|
||||||
|
message.chat.id,
|
||||||
|
message.bot,
|
||||||
|
)
|
||||||
|
|
||||||
error_message = await message.answer(str(e))
|
error_message = await message.answer(str(e))
|
||||||
await state.update_data(previous_message=error_message)
|
await state.update_data(
|
||||||
|
previous_message_id=error_message.message_id,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -114,10 +161,16 @@ async def profile_change_entered(message: Message, state: FSMContext) -> None:
|
|||||||
validated_sex = User().validate_sex(key="sex", value=value)
|
validated_sex = User().validate_sex(key="sex", value=value)
|
||||||
except AssertionError as e:
|
except AssertionError as e:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(
|
||||||
|
state,
|
||||||
|
message.chat.id,
|
||||||
|
message.bot,
|
||||||
|
)
|
||||||
|
|
||||||
error_message = await message.answer(str(e))
|
error_message = await message.answer(str(e))
|
||||||
await state.update_data(previous_message=error_message)
|
await state.update_data(
|
||||||
|
previous_message_id=error_message.message_id,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -127,12 +180,18 @@ async def profile_change_entered(message: Message, state: FSMContext) -> None:
|
|||||||
|
|
||||||
if len(location) != 2:
|
if len(location) != 2:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(
|
||||||
|
state,
|
||||||
|
message.chat.id,
|
||||||
|
message.bot,
|
||||||
|
)
|
||||||
|
|
||||||
error_message = await message.answer(
|
error_message = await message.answer(
|
||||||
messages.VALIDATION_ERROR_MESSAGE,
|
messages.VALIDATION_ERROR_MESSAGE,
|
||||||
)
|
)
|
||||||
await state.update_data(previous_message=error_message)
|
await state.update_data(
|
||||||
|
previous_message_id=error_message.message_id,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -145,10 +204,16 @@ async def profile_change_entered(message: Message, state: FSMContext) -> None:
|
|||||||
)
|
)
|
||||||
except AssertionError as e:
|
except AssertionError as e:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(
|
||||||
|
state,
|
||||||
|
message.chat.id,
|
||||||
|
message.bot,
|
||||||
|
)
|
||||||
|
|
||||||
error_message = await message.answer(str(e))
|
error_message = await message.answer(str(e))
|
||||||
await state.update_data(previous_message=error_message)
|
await state.update_data(
|
||||||
|
previous_message_id=error_message.message_id,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -159,18 +224,27 @@ async def profile_change_entered(message: Message, state: FSMContext) -> None:
|
|||||||
)
|
)
|
||||||
except AssertionError as e:
|
except AssertionError as e:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(
|
||||||
|
state,
|
||||||
|
message.chat.id,
|
||||||
|
message.bot,
|
||||||
|
)
|
||||||
|
|
||||||
error_message = await message.answer(str(e))
|
error_message = await message.answer(str(e))
|
||||||
await state.update_data(previous_message=error_message)
|
await state.update_data(
|
||||||
|
previous_message_id=error_message.message_id,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(state, message.chat.id, message.bot)
|
||||||
|
|
||||||
await state.update_data(value=[validated_country, validated_city])
|
await state.update_data(
|
||||||
|
value=[validated_country, validated_city],
|
||||||
|
successfully=True,
|
||||||
|
)
|
||||||
|
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(state, message.chat.id, message.bot)
|
||||||
|
|
||||||
state_data = await state.get_data()
|
state_data = await state.get_data()
|
||||||
|
|
||||||
|
|||||||
@@ -13,3 +13,7 @@ class Config:
|
|||||||
"SQLALCHEMY_DATABASE_URI",
|
"SQLALCHEMY_DATABASE_URI",
|
||||||
"sqlite:///database.db",
|
"sqlite:///database.db",
|
||||||
)
|
)
|
||||||
|
REDIS_URL = os.getenv(
|
||||||
|
"REDIS_URL",
|
||||||
|
"redis://localhost:6379",
|
||||||
|
)
|
||||||
|
|||||||
@@ -52,14 +52,14 @@ async def username_handler(message: Message, state: FSMContext) -> None:
|
|||||||
)
|
)
|
||||||
except AssertionError as e:
|
except AssertionError as e:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(state, message.chat.id, message.bot)
|
||||||
|
|
||||||
error_message = await message.answer(str(e))
|
error_message = await message.answer(str(e))
|
||||||
await state.update_data(previous_message=error_message)
|
await state.update_data(previous_message_id=error_message.message_id)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(state, message.chat.id, message.bot)
|
||||||
|
|
||||||
await state.update_data(username=validated_username)
|
await state.update_data(username=validated_username)
|
||||||
await state.set_state(RegistrationForm.age)
|
await state.set_state(RegistrationForm.age)
|
||||||
@@ -84,14 +84,14 @@ async def age_handler(message: Message, state: FSMContext) -> None:
|
|||||||
validated_age = User().validate_age(key="age", value=age)
|
validated_age = User().validate_age(key="age", value=age)
|
||||||
except AssertionError as e:
|
except AssertionError as e:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(state, message.chat.id, message.bot)
|
||||||
|
|
||||||
error_message = await message.answer(str(e))
|
error_message = await message.answer(str(e))
|
||||||
await state.update_data(previous_message=error_message)
|
await state.update_data(previous_message_id=error_message.message_id)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(state, message.chat.id, message.bot)
|
||||||
|
|
||||||
await state.update_data(age=validated_age)
|
await state.update_data(age=validated_age)
|
||||||
await state.set_state(RegistrationForm.sex)
|
await state.set_state(RegistrationForm.sex)
|
||||||
@@ -116,14 +116,14 @@ async def sex_handler(message: Message, state: FSMContext) -> None:
|
|||||||
validated_sex = User().validate_sex(key="sex", value=sex)
|
validated_sex = User().validate_sex(key="sex", value=sex)
|
||||||
except AssertionError as e:
|
except AssertionError as e:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(state, message.chat.id, message.bot)
|
||||||
|
|
||||||
error_message = await message.answer(str(e))
|
error_message = await message.answer(str(e))
|
||||||
await state.update_data(previous_message=error_message)
|
await state.update_data(previous_message_id=error_message.message_id)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(state, message.chat.id, message.bot)
|
||||||
|
|
||||||
await state.update_data(sex=validated_sex)
|
await state.update_data(sex=validated_sex)
|
||||||
await state.set_state(RegistrationForm.bio)
|
await state.set_state(RegistrationForm.bio)
|
||||||
@@ -146,7 +146,7 @@ async def bio_handler(message: Message, state: FSMContext) -> None:
|
|||||||
await state.update_data(bio=None)
|
await state.update_data(bio=None)
|
||||||
await state.set_state(RegistrationForm.location)
|
await state.set_state(RegistrationForm.location)
|
||||||
|
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(state, message.chat.id, message.bot)
|
||||||
|
|
||||||
await message.answer(messages.INPUT_BIO_SKIPPED)
|
await message.answer(messages.INPUT_BIO_SKIPPED)
|
||||||
await message.answer(messages.INPUT_LOCATION)
|
await message.answer(messages.INPUT_LOCATION)
|
||||||
@@ -155,14 +155,20 @@ async def bio_handler(message: Message, state: FSMContext) -> None:
|
|||||||
validated_bio = User().validate_bio(key="bio", value=bio)
|
validated_bio = User().validate_bio(key="bio", value=bio)
|
||||||
except AssertionError as e:
|
except AssertionError as e:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(
|
||||||
|
state,
|
||||||
|
message.chat.id,
|
||||||
|
message.bot,
|
||||||
|
)
|
||||||
|
|
||||||
error_message = await message.answer(str(e))
|
error_message = await message.answer(str(e))
|
||||||
await state.update_data(previous_message=error_message)
|
await state.update_data(
|
||||||
|
previous_message_id=error_message.message_id,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(state, message.chat.id, message.bot)
|
||||||
|
|
||||||
await state.update_data(bio=validated_bio)
|
await state.update_data(bio=validated_bio)
|
||||||
await state.set_state(RegistrationForm.location)
|
await state.set_state(RegistrationForm.location)
|
||||||
@@ -182,10 +188,10 @@ async def location_handler(message: Message, state: FSMContext) -> None:
|
|||||||
|
|
||||||
if len(location) != 2:
|
if len(location) != 2:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(state, message.chat.id, message.bot)
|
||||||
|
|
||||||
error_message = await message.answer(messages.VALIDATION_ERROR_MESSAGE)
|
error_message = await message.answer(messages.VALIDATION_ERROR_MESSAGE)
|
||||||
await state.update_data(previous_message=error_message)
|
await state.update_data(previous_message_id=error_message.message_id)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -198,10 +204,10 @@ async def location_handler(message: Message, state: FSMContext) -> None:
|
|||||||
)
|
)
|
||||||
except AssertionError as e:
|
except AssertionError as e:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(state, message.chat.id, message.bot)
|
||||||
|
|
||||||
error_message = await message.answer(str(e))
|
error_message = await message.answer(str(e))
|
||||||
await state.update_data(previous_message=error_message)
|
await state.update_data(previous_message_id=error_message.message_id)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -212,14 +218,14 @@ async def location_handler(message: Message, state: FSMContext) -> None:
|
|||||||
)
|
)
|
||||||
except AssertionError as e:
|
except AssertionError as e:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(state, message.chat.id, message.bot)
|
||||||
|
|
||||||
error_message = await message.answer(str(e))
|
error_message = await message.answer(str(e))
|
||||||
await state.update_data(previous_message=error_message)
|
await state.update_data(previous_message_id=error_message.message_id)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
await delete_message_from_state(state)
|
await delete_message_from_state(state, message.chat.id, message.bot)
|
||||||
|
|
||||||
await state.update_data(location=[validated_country, validated_city])
|
await state.update_data(location=[validated_country, validated_city])
|
||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
@@ -236,7 +242,9 @@ async def location_handler(message: Message, state: FSMContext) -> None:
|
|||||||
data["country"] = data["location"][0]
|
data["country"] = data["location"][0]
|
||||||
data["city"] = data["location"][1]
|
data["city"] = data["location"][1]
|
||||||
del data["location"]
|
del data["location"]
|
||||||
del data["previous_message"]
|
|
||||||
|
if "previous_message_id" in data:
|
||||||
|
del data["previous_message_id"]
|
||||||
|
|
||||||
session.add(User(**data))
|
session.add(User(**data))
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|||||||
+3
-1
@@ -15,6 +15,7 @@ INPUT_BIO_SKIPPED = "Sure. You can always fill it later."
|
|||||||
INPUT_LOCATION = "Enter your location in this format:\n<i>Format: country, city</i>\n<i>Example: Russia, Moscow</i>"
|
INPUT_LOCATION = "Enter your location in this format:\n<i>Format: country, city</i>\n<i>Example: Russia, Moscow</i>"
|
||||||
INPUT_CALLBACK = "All right, your <b>{key}</b> is set to: <b>{value}</b>"
|
INPUT_CALLBACK = "All right, your <b>{key}</b> is set to: <b>{value}</b>"
|
||||||
VALIDATION_ERROR_MESSAGE = "Invalid input. Please try again."
|
VALIDATION_ERROR_MESSAGE = "Invalid input. Please try again."
|
||||||
|
CANCEL_CHANGE = "<i>Enter /cancel to cancel change.</i>"
|
||||||
|
|
||||||
PROFILE = (
|
PROFILE = (
|
||||||
"<b>Your profile:</b>\n\n"
|
"<b>Your profile:</b>\n\n"
|
||||||
@@ -22,10 +23,11 @@ PROFILE = (
|
|||||||
"\tAge: <b>{age}</b>\n"
|
"\tAge: <b>{age}</b>\n"
|
||||||
"\tSex: <b>{sex}</b>\n"
|
"\tSex: <b>{sex}</b>\n"
|
||||||
"\tCountry: <b>{country}</b>\n"
|
"\tCountry: <b>{country}</b>\n"
|
||||||
"\tCity: <b>{city}</b>"
|
"\tCity: <b>{city}</b>\n"
|
||||||
"\tBio: <b>{bio}</b>\n"
|
"\tBio: <b>{bio}</b>\n"
|
||||||
)
|
)
|
||||||
NOT_SET = "<i>Not set</i>"
|
NOT_SET = "<i>Not set</i>"
|
||||||
EDIT_USERNAME = "Enter your username:\n<i>Allowed characters: a-z, A-Z, 0-9, _</i>\n<i>Length: 5-20 characters</i>"
|
EDIT_USERNAME = "Enter your username:\n<i>Allowed characters: a-z, A-Z, 0-9, _</i>\n<i>Length: 5-20 characters</i>"
|
||||||
EDIT_BIO = "Enter your bio (enter /skip if you want to set it to None):\n<i>Maximum length: 100 characters</i>"
|
EDIT_BIO = "Enter your bio (enter /skip if you want to set it to None):\n<i>Maximum length: 100 characters</i>"
|
||||||
PROFILE_UPDATED = "✅ Profile updated"
|
PROFILE_UPDATED = "✅ Profile updated"
|
||||||
|
CHANGE_CANCELED = "❌ Change canceled"
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
"""Added user model
|
||||||
|
|
||||||
|
Revision ID: 2ed063a74f39
|
||||||
|
Revises:
|
||||||
|
Create Date: 2024-03-21 21:40:05.789793
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '2ed063a74f39'
|
||||||
|
down_revision: Union[str, None] = None
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table(
|
||||||
|
'users',
|
||||||
|
sa.Column('telegram_id', sa.BigInteger(), nullable=False),
|
||||||
|
sa.Column('username', sa.String(length=32), nullable=False),
|
||||||
|
sa.Column('age', sa.SmallInteger(), nullable=False),
|
||||||
|
sa.Column('bio', sa.String(length=100), nullable=True),
|
||||||
|
sa.Column('sex', sa.String(length=6), nullable=True),
|
||||||
|
sa.Column('country', sa.Text(), nullable=False),
|
||||||
|
sa.Column('city', sa.Text(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('telegram_id'),
|
||||||
|
sa.UniqueConstraint('username'),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f('ix_users_telegram_id'), 'users', ['telegram_id'], unique=False
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index(op.f('ix_users_telegram_id'), table_name='users')
|
||||||
|
op.drop_table('users')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
"""Added user model
|
|
||||||
|
|
||||||
Revision ID: 50fa8edaaf94
|
|
||||||
Revises:
|
|
||||||
Create Date: 2024-03-21 18:31:15.864426
|
|
||||||
|
|
||||||
"""
|
|
||||||
from typing import Sequence, Union
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision: str = '50fa8edaaf94'
|
|
||||||
down_revision: Union[str, None] = None
|
|
||||||
branch_labels: Union[str, Sequence[str], None] = None
|
|
||||||
depends_on: Union[str, Sequence[str], None] = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.create_table('users',
|
|
||||||
sa.Column('telegram_id', sa.BigInteger(), nullable=False),
|
|
||||||
sa.Column('username', sa.String(length=20), nullable=False),
|
|
||||||
sa.Column('age', sa.SmallInteger(), nullable=False),
|
|
||||||
sa.Column('bio', sa.String(length=100), nullable=True),
|
|
||||||
sa.Column('sex', sa.String(length=6), nullable=True),
|
|
||||||
sa.Column('country', sa.Text(), nullable=False),
|
|
||||||
sa.Column('city', sa.Text(), nullable=False),
|
|
||||||
sa.PrimaryKeyConstraint('telegram_id'),
|
|
||||||
sa.UniqueConstraint('username')
|
|
||||||
)
|
|
||||||
op.create_index(op.f('ix_users_telegram_id'), 'users', ['telegram_id'], unique=False)
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_index(op.f('ix_users_telegram_id'), table_name='users')
|
|
||||||
op.drop_table('users')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
+3
-3
@@ -18,7 +18,7 @@ class User(Base):
|
|||||||
__tablename__ = "users"
|
__tablename__ = "users"
|
||||||
|
|
||||||
telegram_id = sa.Column(sa.BigInteger, primary_key=True, index=True)
|
telegram_id = sa.Column(sa.BigInteger, primary_key=True, index=True)
|
||||||
username = sa.Column(sa.String(20), nullable=False, unique=True)
|
username = sa.Column(sa.String(32), nullable=False, unique=True)
|
||||||
age = sa.Column(sa.SmallInteger, nullable=False)
|
age = sa.Column(sa.SmallInteger, nullable=False)
|
||||||
bio = sa.Column(sa.String(100), nullable=True)
|
bio = sa.Column(sa.String(100), nullable=True)
|
||||||
sex = sa.Column(sa.String(6), nullable=True)
|
sex = sa.Column(sa.String(6), nullable=True)
|
||||||
@@ -29,7 +29,7 @@ class User(Base):
|
|||||||
def validate_username(self, key, value):
|
def validate_username(self, key, value):
|
||||||
regex_pattern = re.compile(r"^[a-zA-Z0-9_]{5,20}$")
|
regex_pattern = re.compile(r"^[a-zA-Z0-9_]{5,20}$")
|
||||||
|
|
||||||
assert len(value) <= 20, "Username must be 20 characters or fewer."
|
assert len(value) <= 32, "Username must be 20 characters or fewer."
|
||||||
assert len(value) >= 5, "Username must be at least 5 characters."
|
assert len(value) >= 5, "Username must be at least 5 characters."
|
||||||
assert (
|
assert (
|
||||||
re.match(regex_pattern, value) is not None
|
re.match(regex_pattern, value) is not None
|
||||||
@@ -42,7 +42,7 @@ class User(Base):
|
|||||||
|
|
||||||
@validates("age")
|
@validates("age")
|
||||||
def validate_age(self, key, value):
|
def validate_age(self, key, value):
|
||||||
assert str(value).isnumeric(), "Invalid input. Please try again."
|
assert str(value).isdigit(), "Invalid input. Please try again."
|
||||||
value = int(value)
|
value = int(value)
|
||||||
assert value >= 13, "You must be at least 13 years old."
|
assert value >= 13, "You must be at least 13 years old."
|
||||||
assert value <= 120, "You must be less than 120 years old."
|
assert value <= 120, "You must be less than 120 years old."
|
||||||
|
|||||||
+31
-14
@@ -1,12 +1,13 @@
|
|||||||
__all__ = ("RegistrationForm",)
|
__all__ = ("RegistrationForm",)
|
||||||
|
|
||||||
|
from aiogram import Bot
|
||||||
from aiogram.exceptions import TelegramBadRequest
|
from aiogram.exceptions import TelegramBadRequest
|
||||||
from aiogram.fsm.state import State, StatesGroup
|
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
|
from aiogram.fsm.state import State, StatesGroup
|
||||||
|
|
||||||
|
|
||||||
class RegistrationForm(StatesGroup):
|
class RegistrationForm(StatesGroup):
|
||||||
previous_message = State()
|
previous_message_id = State()
|
||||||
username = State()
|
username = State()
|
||||||
age = State()
|
age = State()
|
||||||
bio = State()
|
bio = State()
|
||||||
@@ -15,34 +16,50 @@ class RegistrationForm(StatesGroup):
|
|||||||
|
|
||||||
|
|
||||||
class UserAltering(StatesGroup):
|
class UserAltering(StatesGroup):
|
||||||
successfully = State()
|
|
||||||
message_id = State()
|
|
||||||
input_message = State()
|
|
||||||
previous_message = State()
|
|
||||||
column = State()
|
column = State()
|
||||||
value = State()
|
value = State()
|
||||||
|
message_id = State()
|
||||||
|
input_message_id = State()
|
||||||
|
previous_message_id = State()
|
||||||
|
successfully = State()
|
||||||
|
|
||||||
|
|
||||||
async def delete_message_from_state(state: FSMContext) -> None:
|
async def delete_message_from_state(
|
||||||
|
state: FSMContext,
|
||||||
|
chat_id: int,
|
||||||
|
bot: Bot | None,
|
||||||
|
) -> None:
|
||||||
|
if bot is None:
|
||||||
|
return
|
||||||
|
|
||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
|
|
||||||
if "previous_message" in data and data["previous_message"] is not None:
|
if (
|
||||||
|
"previous_message_id" in data
|
||||||
|
and data["previous_message_id"] is not None
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
await data["previous_message"].delete()
|
await bot.delete_message(
|
||||||
|
message_id=data["previous_message_id"],
|
||||||
|
chat_id=chat_id,
|
||||||
|
)
|
||||||
except TelegramBadRequest:
|
except TelegramBadRequest:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
await state.update_data(previous_message=None)
|
await state.update_data(previous_message_id=None)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
"input_message" in data
|
"input_message_id" in data
|
||||||
and data["input_message"] is not None
|
and data["input_message_id"] is not None
|
||||||
and "successfully" in data
|
and "successfully" in data
|
||||||
and data["successfully"]
|
and data["successfully"]
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
await data["input_message"].delete()
|
await bot.delete_message(
|
||||||
|
message_id=data["input_message_id"],
|
||||||
|
chat_id=chat_id,
|
||||||
|
)
|
||||||
except TelegramBadRequest:
|
except TelegramBadRequest:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
await state.update_data(info_message=None)
|
await state.update_data(input_message_id=None)
|
||||||
|
|||||||
+49
-9
@@ -1,22 +1,62 @@
|
|||||||
name: travel_agent_bot
|
name: travel_agent_bot
|
||||||
version: '3'
|
version: "3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
app:
|
|
||||||
build: .
|
|
||||||
container_name: bot
|
|
||||||
depends_on:
|
|
||||||
- postgres
|
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:latest
|
image: postgres:latest
|
||||||
container_name: db
|
container_name: postgres
|
||||||
|
healthcheck:
|
||||||
|
test: pg_isready -U postgres -h localhost
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: postgres
|
POSTGRES_DB: postgres
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: wTAb5KoZ4dBtscg
|
POSTGRES_PASSWORD: wTAb5KoZ4dBtscg
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:latest
|
||||||
|
container_name: redis
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
container_name: bot
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
BOT_TOKEN: ${BOT_TOKEN}
|
||||||
|
REDIS_URL: redis://redis:6379/
|
||||||
|
SQLALCHEMY_DATABASE_URI: postgresql://postgres:wTAb5KoZ4dBtscg@postgres:5432/postgres
|
||||||
|
entrypoint: ["bash", "-c"]
|
||||||
|
command: ["alembic -c app/alembic.ini upgrade head && python -m app"]
|
||||||
|
|
||||||
|
pgadmin:
|
||||||
|
image: dpage/pgadmin4
|
||||||
|
container_name: pgadmin
|
||||||
|
environment:
|
||||||
|
PGADMIN_DEFAULT_EMAIL: admin@mail.com
|
||||||
|
PGADMIN_DEFAULT_PASSWORD: admin
|
||||||
|
ports:
|
||||||
|
- "5050:80"
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- pgadmin_data:/var/lib/pgadmin
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
travel_agent_bot_data:
|
postgres_data:
|
||||||
|
redis_data:
|
||||||
|
pgadmin_data:
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ cachetools==5.3.3
|
|||||||
geopy==2.4.1
|
geopy==2.4.1
|
||||||
psycopg2-binary==2.9.9
|
psycopg2-binary==2.9.9
|
||||||
python-dotenv==1.0.1
|
python-dotenv==1.0.1
|
||||||
|
redis==5.0.3
|
||||||
sqlalchemy==2.0.28
|
sqlalchemy==2.0.28
|
||||||
|
|||||||
Reference in New Issue
Block a user