feat: Added redis as storage for states, improvements in docker compose, added /cancel command, small improvements

This commit is contained in:
ITQ
2024-03-21 23:16:46 +03:00
parent 597d629954
commit b655adafe6
12 changed files with 282 additions and 135 deletions
-5
View File
@@ -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
View File
@@ -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
View File
@@ -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()
+4
View File
@@ -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",
)
+29 -21
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+1
View File
@@ -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