This commit is contained in:
2026-02-27 17:24:37 +03:00
parent d86276f56d
commit e9098f7bc1
13 changed files with 184 additions and 17 deletions
+3 -3
View File
@@ -7,9 +7,9 @@ from alembic import context
from sqlalchemy.engine import Connection
from sqlalchemy.ext.asyncio import async_engine_from_config
from src.dutylog.infrastructure.database.models.base import Base
from src.dutylog.infrastructure.database.models.user import User as User
from src.dutylog.infrastructure.utils.config import load_config
from dutylog.infrastructure.database.models.base import Base
from dutylog.infrastructure.database.models import *
from dutylog.infrastructure.utils.config import load_config
config = context.config
@@ -0,0 +1,56 @@
"""add models
Revision ID: fc1269fbc645
Revises: f012f3ef4b65
Create Date: 2026-02-27 17:24:00.349948
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'fc1269fbc645'
down_revision: Union[str, Sequence[str], None] = 'f012f3ef4b65'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('hours_transactions',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('user_id', sa.BigInteger(), nullable=False),
sa.Column('transaction_type', sa.String(length=50), nullable=False),
sa.Column('amount', sa.Integer(), nullable=False),
sa.Column('admin_id', sa.BigInteger(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['admin_id'], ['users.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.add_column('users', sa.Column('first_name', sa.String(length=255), nullable=True))
op.add_column('users', sa.Column('last_name', sa.String(length=255), nullable=True))
op.add_column('users', sa.Column('is_admin', sa.Boolean(), server_default='false', nullable=False))
op.add_column('users', sa.Column('active_hours', sa.Integer(), server_default='0', nullable=False))
op.add_column('users', sa.Column('inactive_hours', sa.Integer(), server_default='0', nullable=False))
op.add_column('users', sa.Column('created_at', sa.DateTime(timezone=True), nullable=False))
op.add_column('users', sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('users', 'updated_at')
op.drop_column('users', 'created_at')
op.drop_column('users', 'inactive_hours')
op.drop_column('users', 'active_hours')
op.drop_column('users', 'is_admin')
op.drop_column('users', 'last_name')
op.drop_column('users', 'first_name')
op.drop_table('hours_transactions')
# ### end Alembic commands ###
+4 -4
View File
@@ -8,10 +8,10 @@ from aiogram_dialog import setup_dialogs
from dishka import make_async_container
from dishka.integrations.aiogram import setup_dishka
from src.dutylog.application.bot.user_handlers import router as user_router
from src.dutylog.application.bot.user_dialogs import main_menu_dialog
from src.dutylog.infrastructure.ioc import ConfigProvider, DatabaseProvider, DAOProvider
from src.dutylog.infrastructure.utils.config import load_config
from dutylog.application.bot.user_handlers import router as user_router
from dutylog.application.bot.user_dialogs import main_menu_dialog
from dutylog.infrastructure.ioc import ConfigProvider, DatabaseProvider, DAOProvider
from dutylog.infrastructure.utils.config import load_config
async def main():
@@ -1,3 +1,3 @@
from src.dutylog.application.bot.user_dialogs.main_menu_dialog import main_menu_dialog
from dutylog.application.bot.user_dialogs.main_menu_dialog import main_menu_dialog
__all__ = ["main_menu_dialog"]
@@ -1,7 +1,7 @@
from aiogram_dialog import Dialog, Window
from aiogram_dialog.widgets.text import Const
from src.dutylog.application.bot.user_dialogs.states import MainMenuSG
from dutylog.application.bot.user_dialogs.states import MainMenuSG
main_menu_dialog = Dialog(
Window(
+1 -1
View File
@@ -3,7 +3,7 @@ from aiogram.filters import CommandStart
from aiogram.types import Message
from aiogram_dialog import DialogManager, StartMode
from src.dutylog.application.bot.user_dialogs.states import MainMenuSG
from dutylog.application.bot.user_dialogs.states import MainMenuSG
router = Router()
@@ -0,0 +1,41 @@
from sqlalchemy import select, delete
from sqlalchemy.ext.asyncio import AsyncSession
from dutylog.infrastructure.database.models.hours_transaction import HoursTransaction
class HoursTransactionsDAO:
def __init__(self, session: AsyncSession):
self.session = session
async def get_by_id(self, transaction_id: int) -> HoursTransaction | None:
result = await self.session.execute(
select(HoursTransaction).where(HoursTransaction.id == transaction_id)
)
return result.scalar_one_or_none()
async def get_all(self) -> list[HoursTransaction]:
result = await self.session.execute(
select(HoursTransaction).order_by(HoursTransaction.created_at.desc())
)
return list(result.scalars().all())
async def get_by_user_id(self, user_id: int) -> list[HoursTransaction]:
result = await self.session.execute(
select(HoursTransaction)
.where(HoursTransaction.user_id == user_id)
.order_by(HoursTransaction.created_at.desc())
)
return list(result.scalars().all())
async def create(self, transaction: HoursTransaction) -> HoursTransaction:
self.session.add(transaction)
await self.session.commit()
await self.session.refresh(transaction)
return transaction
async def delete(self, transaction_id: int) -> None:
await self.session.execute(
delete(HoursTransaction).where(HoursTransaction.id == transaction_id)
)
await self.session.commit()
@@ -1,21 +1,38 @@
from sqlalchemy import select
from sqlalchemy import select, update, delete
from sqlalchemy.ext.asyncio import AsyncSession
from src.dutylog.infrastructure.database.models.user import User
from dutylog.infrastructure.database.models.user import User
class UsersDAO:
def __init__(self, session: AsyncSession):
self.session = session
async def get_user(self, user_id: int) -> User | None:
async def get_by_id(self, user_id: int) -> User | None:
result = await self.session.execute(
select(User).where(User.id == user_id)
)
return result.scalar_one_or_none()
async def create_user(self, user_id: int, username: str | None) -> User:
user = User(id=user_id, username=username)
async def get_all(self) -> list[User]:
result = await self.session.execute(select(User))
return list(result.scalars().all())
async def create(self, user: User) -> User:
self.session.add(user)
await self.session.commit()
await self.session.refresh(user)
return user
async def update(self, user_id: int, **kwargs) -> User | None:
await self.session.execute(
update(User).where(User.id == user_id).values(**kwargs)
)
await self.session.commit()
return await self.get_by_id(user_id)
async def delete(self, user_id: int) -> None:
await self.session.execute(
delete(User).where(User.id == user_id)
)
await self.session.commit()
@@ -0,0 +1,5 @@
from dutylog.infrastructure.database.models.base import Base
from dutylog.infrastructure.database.models.user import User
from dutylog.infrastructure.database.models.hours_transaction import HoursTransaction
__all__ = ["Base", "User", "HoursTransaction"]
@@ -0,0 +1,24 @@
from datetime import datetime, timezone, timedelta
from enum import Enum
from sqlalchemy import BigInteger, Integer, String, DateTime, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column
from dutylog.infrastructure.database.models.base import Base
from dutylog.infrastructure.utils.datetime import msk_now
class TransactionType(str, Enum):
INCREASE = "increase"
DECREASE = "decrease"
class HoursTransaction(Base):
__tablename__ = "hours_transactions"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
user_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
transaction_type: Mapped[str] = mapped_column(String(50), nullable=False)
amount: Mapped[int] = mapped_column(Integer, nullable=False)
admin_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=msk_now)
@@ -1,7 +1,10 @@
from sqlalchemy import BigInteger, String
from datetime import datetime
from sqlalchemy import BigInteger, Boolean, Integer, String, DateTime
from sqlalchemy.orm import Mapped, mapped_column
from src.dutylog.infrastructure.database.models.base import Base
from dutylog.infrastructure.database.models.base import Base
from dutylog.infrastructure.utils.datetime import msk_now
class User(Base):
@@ -9,3 +12,10 @@ class User(Base):
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
username: Mapped[str | None] = mapped_column(String(255), nullable=True)
first_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
last_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
is_admin: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
active_hours: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
inactive_hours: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=msk_now)
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=msk_now, onupdate=msk_now)
+6
View File
@@ -5,6 +5,7 @@ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
from dutylog.infrastructure.database.config import create_engine, create_session_maker
from dutylog.infrastructure.database.dao.users_dao import UsersDAO
from dutylog.infrastructure.database.dao.hours_transactions_dao import HoursTransactionsDAO
from dutylog.infrastructure.utils.config import Config, load_config
@@ -36,3 +37,8 @@ class DAOProvider(Provider):
def get_users_dao(self, session: AsyncSession) -> UsersDAO:
return UsersDAO(session)
@provide(scope=Scope.REQUEST)
def get_hours_transactions_dao(self, session: AsyncSession) -> HoursTransactionsDAO:
return HoursTransactionsDAO(session)
@@ -0,0 +1,8 @@
from datetime import datetime, timezone, timedelta
MSK_TZ = timezone(timedelta(hours=3))
def msk_now() -> datetime:
return datetime.now(MSK_TZ)