diff --git a/alembic/versions/a879badde4a5_add_name_to_user.py b/alembic/versions/a879badde4a5_add_name_to_user.py
new file mode 100644
index 0000000..dd8dd66
--- /dev/null
+++ b/alembic/versions/a879badde4a5_add_name_to_user.py
@@ -0,0 +1,25 @@
+"""add_name_to_user
+
+Revision ID: a879badde4a5
+Revises: 520eccd2e55f
+Create Date: 2026-01-02 21:21:22.159248
+
+"""
+from collections.abc import Sequence
+
+from alembic import op
+import sqlalchemy as sa
+
+
+revision: str = 'a879badde4a5'
+down_revision: str | None = '520eccd2e55f'
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
+
+
+def upgrade() -> None:
+ op.add_column('users', sa.Column('name', sa.String(length=128), nullable=True))
+
+
+def downgrade() -> None:
+ op.drop_column('users', 'name')
diff --git a/src/trudex/application/bot/admin_dialogs/users.py b/src/trudex/application/bot/admin_dialogs/users.py
index a9c2f20..34a08b6 100644
--- a/src/trudex/application/bot/admin_dialogs/users.py
+++ b/src/trudex/application/bot/admin_dialogs/users.py
@@ -18,7 +18,7 @@ async def get_users_data(user_dao: FromDishka[UserDAO], **_kwargs):
return {
"users": [
- (f"{u.first_name} (@{u.username or 'нет'})", u.id)
+ (f"{u.name or u.first_name} (@{u.username or 'нет'})", u.id)
for u in users
],
"count": len(users),
@@ -37,6 +37,7 @@ async def get_user_detail_data(dialog_manager: DialogManager, user_dao: FromDish
username_str = f"@{user.username}" if user.username else "—"
last_name_str = user.last_name or "—"
+ name_str = user.name or "—"
group_str = str(user.group) if user.group else "—"
admin_status = "✅ Да" if user.is_admin else "❌ Нет"
@@ -45,6 +46,7 @@ async def get_user_detail_data(dialog_manager: DialogManager, user_dao: FromDish
f"ID: {user.id}\n"
f"Имя: {user.first_name}\n"
f"Фамилия: {last_name_str}\n"
+ f"Имя и фамилия: {name_str}\n"
f"Username: {username_str}\n"
f"Группа: {group_str}\n"
f"Администратор: {admin_status}"
diff --git a/src/trudex/application/bot/creator_dialogs/users.py b/src/trudex/application/bot/creator_dialogs/users.py
index a42b207..9b92fb8 100644
--- a/src/trudex/application/bot/creator_dialogs/users.py
+++ b/src/trudex/application/bot/creator_dialogs/users.py
@@ -18,7 +18,7 @@ async def get_users_data(user_dao: FromDishka[UserDAO], **_kwargs):
return {
"users": [
- (f"{u.first_name} (@{u.username or 'нет'})", u.id)
+ (f"{u.name or u.first_name} (@{u.username or 'нет'})", u.id)
for u in users
],
"count": len(users),
@@ -36,15 +36,15 @@ async def get_user_detail_data(dialog_manager: DialogManager, user_dao: FromDish
return {"user_info": "Пользователь не найден", "is_admin": True, "show_make_admin": False}
username_str = f"@{user.username}" if user.username else "—"
- last_name_str = user.last_name or "—"
+ name_str = user.name or "—"
group_str = str(user.group) if user.group else "—"
admin_status = "✅ Да" if user.is_admin else "❌ Нет"
user_info = (
f"👤 Информация о пользователе\n\n"
f"ID: {user.id}\n"
- f"Имя: {user.first_name}\n"
- f"Фамилия: {last_name_str}\n"
+ f"Ник: {user.first_name}\n"
+ f"Имя и фамилия: {name_str}\n"
f"Username: {username_str}\n"
f"Группа: {group_str}\n"
f"Администратор: {admin_status}"
@@ -68,8 +68,9 @@ async def get_confirm_data(dialog_manager: DialogManager, user_dao: FromDishka[U
return {"user_info": "Пользователь не найден"}
username_str = f"@{user.username}" if user.username else "—"
+ name_str = user.name or f"{user.first_name} {user.last_name or ''}".strip()
return {
- "user_info": f"{user.first_name}\n{username_str}\nID: {user.id}"
+ "user_info": f"{name_str}\n{username_str}\nID: {user.id}"
}
diff --git a/src/trudex/application/bot/handlers.py b/src/trudex/application/bot/handlers.py
index c19724e..663f378 100644
--- a/src/trudex/application/bot/handlers.py
+++ b/src/trudex/application/bot/handlers.py
@@ -32,7 +32,7 @@ async def start_handler(
groups = await group_dao.get_all()
if len(groups) > 0:
- # Есть группы - создаем пользователя без группы и показываем выбор
+ # Есть группы - создаем пользователя без группы и имени, показываем регистрацию
await user_dao.create(
user_id=message.from_user.id,
first_name=message.from_user.first_name,
@@ -40,7 +40,7 @@ async def start_handler(
last_name=message.from_user.last_name,
)
await dialog_manager.start(
- UserRegistrationSG.select_group,
+ UserRegistrationSG.input_name,
mode=StartMode.RESET_STACK,
data={"user_id": message.from_user.id}
)
@@ -55,18 +55,27 @@ async def start_handler(
await dialog_manager.start(UserMenuSG.main, mode=StartMode.RESET_STACK)
else:
# Существующий пользователь
- # Проверяем, выбрал ли он группу
+ # Проверяем, заполнил ли он имя и группу
groups = await group_dao.get_all()
- if len(groups) > 0 and existing_user.group is None:
- # Есть группы, но пользователь не выбрал группу - показываем выбор
- await dialog_manager.start(
- UserRegistrationSG.select_group,
- mode=StartMode.RESET_STACK,
- data={"user_id": message.from_user.id}
- )
+ if len(groups) > 0 and (existing_user.name is None or existing_user.group is None):
+ # Есть группы, но пользователь не завершил регистрацию
+ if existing_user.name is None:
+ # Начинаем с ввода имени
+ await dialog_manager.start(
+ UserRegistrationSG.input_name,
+ mode=StartMode.RESET_STACK,
+ data={"user_id": message.from_user.id}
+ )
+ else:
+ # Имя есть, но нет группы
+ await dialog_manager.start(
+ UserRegistrationSG.select_group,
+ mode=StartMode.RESET_STACK,
+ data={"user_id": message.from_user.id}
+ )
else:
- # Группа выбрана или групп нет - обновляем данные и открываем меню
+ # Регистрация завершена или групп нет - обновляем данные и открываем меню
await user_dao.upsert(
user_id=message.from_user.id,
first_name=message.from_user.first_name,
diff --git a/src/trudex/application/bot/user_dialogs/registration.py b/src/trudex/application/bot/user_dialogs/registration.py
index d1d77f0..632e983 100644
--- a/src/trudex/application/bot/user_dialogs/registration.py
+++ b/src/trudex/application/bot/user_dialogs/registration.py
@@ -1,5 +1,6 @@
-from aiogram.types import CallbackQuery
+from aiogram.types import CallbackQuery, Message
from aiogram_dialog import Dialog, DialogManager, StartMode, Window
+from aiogram_dialog.widgets.input import MessageInput
from aiogram_dialog.widgets.kbd import ScrollingGroup, Select
from aiogram_dialog.widgets.text import Const, Format
from dishka import FromDishka
@@ -11,6 +12,28 @@ from trudex.infrastructure.database.dao.group import GroupDAO
from trudex.infrastructure.database.dao.user import UserDAO
+@inject
+async def on_name_input(message: Message, _widget: MessageInput, manager: DialogManager, user_dao: FromDishka[UserDAO]):
+ if not message.text:
+ await message.answer("❌ Имя и фамилия не могут быть пустыми")
+ return
+
+ name = message.text.strip()
+ if not name:
+ await message.answer("❌ Имя и фамилия не могут быть пустыми")
+ return
+
+ if len(name) > 128:
+ await message.answer("❌ Имя и фамилия слишком длинные (максимум 128 символов)")
+ return
+
+ user_id = manager.start_data.get("user_id")
+ await user_dao.update(user_id=user_id, name=name)
+
+ manager.dialog_data["name"] = name
+ await manager.switch_to(UserRegistrationSG.select_group)
+
+
@inject
async def get_groups_for_registration(dialog_manager: DialogManager, group_dao: FromDishka[GroupDAO], **_kwargs):
groups = await group_dao.get_all()
@@ -34,7 +57,15 @@ registration_dialog = Dialog(
Window(
Const(
"👋 Добро пожаловать!\n\n"
- "🎓 Выберите вашу группу:\n\n"
+ "✏️ Введите ваше имя и фамилию:\n\n"
+ "⚠️ Внимание: Изменить данные можно будет только через 24 часа!"
+ ),
+ MessageInput(on_name_input),
+ state=UserRegistrationSG.input_name,
+ ),
+ Window(
+ Const(
+ "🎓 Выберите вашу группу:\n\n"
"⚠️ Внимание: Изменить группу можно будет только через 24 часа!"
),
ScrollingGroup(
diff --git a/src/trudex/application/bot/user_dialogs/states.py b/src/trudex/application/bot/user_dialogs/states.py
index e89b889..efb7e10 100644
--- a/src/trudex/application/bot/user_dialogs/states.py
+++ b/src/trudex/application/bot/user_dialogs/states.py
@@ -6,4 +6,5 @@ class UserMenuSG(StatesGroup):
class UserRegistrationSG(StatesGroup):
+ input_name = State()
select_group = State()
diff --git a/src/trudex/domain/schemas.py b/src/trudex/domain/schemas.py
index f8c4634..40c5949 100644
--- a/src/trudex/domain/schemas.py
+++ b/src/trudex/domain/schemas.py
@@ -8,6 +8,7 @@ class User:
first_name: str
username: str | None = None
last_name: str | None = None
+ name: str | None = None
group: int | None = None
is_admin: bool = False
created_at: datetime | None = None
diff --git a/src/trudex/infrastructure/database/dao/test.py b/src/trudex/infrastructure/database/dao/test.py
index 6a67452..88266b5 100644
--- a/src/trudex/infrastructure/database/dao/test.py
+++ b/src/trudex/infrastructure/database/dao/test.py
@@ -18,7 +18,9 @@ class TestDAO:
return TestDTO(model).to_domain() if model else None
async def get_all(self) -> list[DomainTest]:
- result = await self.session.execute(select(Test))
+ result = await self.session.execute(
+ select(Test).order_by(Test.id)
+ )
models = list(result.scalars().all())
return [TestDTO(model).to_domain() for model in models]
diff --git a/src/trudex/infrastructure/database/dao/user.py b/src/trudex/infrastructure/database/dao/user.py
index 8464bf9..a515a8d 100644
--- a/src/trudex/infrastructure/database/dao/user.py
+++ b/src/trudex/infrastructure/database/dao/user.py
@@ -28,6 +28,7 @@ class UserDAO:
first_name: str,
username: str | None = None,
last_name: str | None = None,
+ name: str | None = None,
group: int | None = None,
is_admin: bool = False,
) -> DomainUser:
@@ -36,6 +37,7 @@ class UserDAO:
username=username,
first_name=first_name,
last_name=last_name,
+ name=name,
group=group,
is_admin=is_admin,
)
@@ -50,6 +52,7 @@ class UserDAO:
username: str | None = None,
first_name: str | None = None,
last_name: str | None = None,
+ name: str | None = None,
group: int | None = None,
is_admin: bool | None = None,
) -> DomainUser | None:
@@ -66,6 +69,8 @@ class UserDAO:
user.first_name = first_name
if last_name is not None:
user.last_name = last_name
+ if name is not None:
+ user.name = name
if group is not None:
user.group = group
if is_admin is not None:
@@ -93,6 +98,7 @@ class UserDAO:
first_name: str,
username: str | None = None,
last_name: str | None = None,
+ name: str | None = None,
group: int | None = None,
is_admin: bool = False,
) -> DomainUser:
@@ -108,6 +114,8 @@ class UserDAO:
user.first_name = first_name
if last_name is not None:
user.last_name = last_name
+ if name is not None:
+ user.name = name
if group is not None:
user.group = group
if is_admin is not None:
@@ -121,6 +129,7 @@ class UserDAO:
username=username,
first_name=first_name,
last_name=last_name,
+ name=name,
group=group,
is_admin=is_admin,
)
diff --git a/src/trudex/infrastructure/database/dto/user.py b/src/trudex/infrastructure/database/dto/user.py
index 5e69349..4354ed6 100644
--- a/src/trudex/infrastructure/database/dto/user.py
+++ b/src/trudex/infrastructure/database/dto/user.py
@@ -12,6 +12,7 @@ class UserDTO:
username=self.model.username,
first_name=self.model.first_name,
last_name=self.model.last_name,
+ name=self.model.name,
group=self.model.group,
is_admin=self.model.is_admin,
created_at=self.model.created_at,
diff --git a/src/trudex/infrastructure/database/models.py b/src/trudex/infrastructure/database/models.py
index 2d03310..123f9e6 100644
--- a/src/trudex/infrastructure/database/models.py
+++ b/src/trudex/infrastructure/database/models.py
@@ -19,6 +19,7 @@ class User(Base):
username: Mapped[str | None] = mapped_column(String(32))
first_name: Mapped[str] = mapped_column(String(64))
last_name: Mapped[str | None] = mapped_column(String(64))
+ name: Mapped[str | None] = mapped_column(String(128))
group: Mapped[int | None] = mapped_column(CheckConstraint("group >= 1000 AND group <= 9999"))
is_admin: Mapped[bool] = mapped_column(default=False)
created_at: Mapped[datetime] = mapped_column(server_default=func.now())