mirror of
https://github.com/koloideal/Quizzi.git
synced 2026-06-10 02:15:29 +03:00
update
This commit is contained in:
+4
-5
@@ -59,11 +59,10 @@ def do_run_migrations(connection: Connection):
|
||||
|
||||
|
||||
async def run_async_migrations() -> None:
|
||||
"""In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
connectable = create_async_engine(db_config.url)
|
||||
connectable = create_async_engine(
|
||||
db_config.url,
|
||||
connect_args={"server_settings": {"timezone": "UTC"}},
|
||||
)
|
||||
|
||||
async with connectable.connect() as connection:
|
||||
await connection.run_sync(do_run_migrations)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
"""set_timezone_to_utc
|
||||
|
||||
Revision ID: 2ba7282de7d9
|
||||
Revises: c4d5e6f7a8b9
|
||||
Create Date: 2026-02-20 01:14:15.088425
|
||||
|
||||
"""
|
||||
from collections.abc import Sequence
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision: str = '2ba7282de7d9'
|
||||
down_revision: str | None = 'c4d5e6f7a8b9'
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute("SET timezone = 'UTC'")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
pass
|
||||
@@ -1,4 +1,4 @@
|
||||
from datetime import date, datetime, time
|
||||
from datetime import date, datetime, time, timezone
|
||||
|
||||
from aiogram.types import CallbackQuery, ContentType, Message
|
||||
from aiogram_dialog import Dialog, DialogManager, StartMode, Window
|
||||
@@ -139,7 +139,8 @@ async def on_skip_time_limit(_callback: CallbackQuery, _button: Button, manager:
|
||||
|
||||
|
||||
async def on_date_selected(_callback, _widget, manager: DialogManager, selected_date: date):
|
||||
manager.dialog_data["expires_at"] = datetime.combine(selected_date, time.min)
|
||||
expires_at = datetime.combine(selected_date, time.min, tzinfo=timezone.utc).replace(tzinfo=None)
|
||||
manager.dialog_data["expires_at"] = expires_at
|
||||
await manager.switch_to(SharedCreateTestSG.input_for_group)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import asyncio
|
||||
import functools
|
||||
from datetime import date, datetime, time
|
||||
from datetime import date, datetime, time, timezone
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram.types import BufferedInputFile, CallbackQuery, Message
|
||||
@@ -520,7 +520,7 @@ async def on_date_selected_for_test(
|
||||
await _callback.answer("❌ Тест не найден")
|
||||
return
|
||||
|
||||
expires_at = datetime.combine(selected_date, time.min)
|
||||
expires_at = datetime.combine(selected_date, time.min, tzinfo=timezone.utc).replace(tzinfo=None)
|
||||
result = await test_service.update_expires(test_id, expires_at)
|
||||
await _callback.answer(result.message)
|
||||
await manager.switch_to(SharedTestsSG.test_detail)
|
||||
|
||||
@@ -12,7 +12,7 @@ from quizzi.infrastructure.database.models import QuestionType
|
||||
from quizzi.infrastructure.database.repo.test import TestRepository
|
||||
from quizzi.infrastructure.database.repo.test_attempt import TestAttemptRepository
|
||||
from quizzi.infrastructure.utils.rate_limiter import PasswordRateLimiter
|
||||
from quizzi.infrastructure.utils.timezone import now_msk_naive, to_msk
|
||||
from quizzi.infrastructure.utils.timezone import now_utc_naive, to_msk
|
||||
|
||||
|
||||
@inject
|
||||
@@ -137,7 +137,7 @@ async def start_test_without_password(
|
||||
return
|
||||
|
||||
attempt = await attempt_repo.attempt_dao.create(user_id=user_id, test_id=test_id)
|
||||
started_at = now_msk_naive()
|
||||
started_at = now_utc_naive()
|
||||
|
||||
first_question, _ = await test_repo.get_question_with_options(questions[0].id)
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ from quizzi.infrastructure.database.repo.test_attempt import TestAttemptReposito
|
||||
from quizzi.infrastructure.utils.config import Config
|
||||
from quizzi.infrastructure.utils.qr_generator import generate_qr_bytes
|
||||
from quizzi.infrastructure.utils.test_id_to_hash import encode_id
|
||||
from quizzi.infrastructure.utils.timezone import now_msk, now_msk_naive, to_msk
|
||||
from quizzi.infrastructure.utils.timezone import now_utc, now_utc_naive, to_msk
|
||||
|
||||
|
||||
def can_edit_field(updated_at: datetime | None) -> bool:
|
||||
@@ -28,13 +28,17 @@ def can_edit_field(updated_at: datetime | None) -> bool:
|
||||
return True
|
||||
updated_at_msk = to_msk(updated_at)
|
||||
assert updated_at_msk is not None
|
||||
return now_msk() - updated_at_msk >= timedelta(hours=24)
|
||||
now_msk = to_msk(now_utc())
|
||||
assert now_msk is not None
|
||||
return now_msk - updated_at_msk >= timedelta(hours=24)
|
||||
|
||||
|
||||
def get_remaining_time(updated_at: datetime) -> str:
|
||||
updated_at_msk = to_msk(updated_at)
|
||||
assert updated_at_msk is not None
|
||||
remaining = timedelta(hours=24) - (now_msk() - updated_at_msk)
|
||||
now_msk = to_msk(now_utc())
|
||||
assert now_msk is not None
|
||||
remaining = timedelta(hours=24) - (now_msk - updated_at_msk)
|
||||
hours = int(remaining.total_seconds() // 3600)
|
||||
minutes = int((remaining.total_seconds() % 3600) // 60)
|
||||
return f"{hours}ч {minutes}м"
|
||||
@@ -149,7 +153,7 @@ async def on_name_input(
|
||||
result = await user_dao.update(
|
||||
user_id=message.from_user.id,
|
||||
name=name,
|
||||
name_updated_at=now_msk_naive(),
|
||||
name_updated_at=now_utc_naive(),
|
||||
)
|
||||
if result:
|
||||
await message.answer("✅ Имя обновлено")
|
||||
@@ -176,7 +180,7 @@ async def on_group_selected(
|
||||
result = await user_dao.update(
|
||||
user_id=_callback.from_user.id,
|
||||
group=int(item_id),
|
||||
group_updated_at=now_msk_naive(),
|
||||
group_updated_at=now_utc_naive(),
|
||||
)
|
||||
if result:
|
||||
await _callback.answer("✅ Группа обновлена")
|
||||
|
||||
@@ -9,7 +9,7 @@ from dishka.integrations.aiogram_dialog import inject
|
||||
from quizzi.application.bot.user_dialogs.states import UserDeeplinkSG, UserMenuSG, UserRegistrationSG
|
||||
from quizzi.infrastructure.database.dao.group import GroupDAO
|
||||
from quizzi.infrastructure.database.dao.user import UserDAO
|
||||
from quizzi.infrastructure.utils.timezone import now_msk_naive
|
||||
from quizzi.infrastructure.utils.timezone import now_utc_naive
|
||||
|
||||
|
||||
@inject
|
||||
@@ -40,7 +40,7 @@ async def on_name_input(
|
||||
pending_test_id = start_data.get("pending_test_id")
|
||||
|
||||
if user_id:
|
||||
await user_dao.update(user_id=user_id, name=name, name_updated_at=now_msk_naive())
|
||||
await user_dao.update(user_id=user_id, name=name, name_updated_at=now_utc_naive())
|
||||
|
||||
manager.dialog_data["name"] = name
|
||||
|
||||
@@ -80,7 +80,7 @@ async def on_group_selected(
|
||||
pending_test_id = start_data.get("pending_test_id")
|
||||
|
||||
if user_id:
|
||||
await user_dao.update(user_id=user_id, group=int(item_id), group_updated_at=now_msk_naive())
|
||||
await user_dao.update(user_id=user_id, group=int(item_id), group_updated_at=now_utc_naive())
|
||||
|
||||
if pending_test_id:
|
||||
await manager.start(
|
||||
|
||||
@@ -196,7 +196,7 @@ async def on_start_test(
|
||||
await _callback.answer("❌ Тест деактивирован")
|
||||
return
|
||||
|
||||
if test.expires_at and test.expires_at < now_msk_naive():
|
||||
if test.expires_at and test.expires_at < now_utc_naive():
|
||||
await _callback.answer("❌ Срок действия теста истек")
|
||||
return
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ def new_session_maker(db_url: str) -> async_sessionmaker[AsyncSession]:
|
||||
max_overflow=15,
|
||||
connect_args={
|
||||
"timeout": 5,
|
||||
"server_settings": {"timezone": "UTC"},
|
||||
},
|
||||
)
|
||||
return async_sessionmaker(engine, class_=AsyncSession, autoflush=False, expire_on_commit=False)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@@ -34,7 +34,7 @@ class TestAttemptDAO:
|
||||
attempt = TestAttempt(
|
||||
user_id=user_id,
|
||||
test_id=test_id,
|
||||
started_at=datetime.utcnow(),
|
||||
started_at=datetime.now(timezone.utc).replace(tzinfo=None),
|
||||
score=score,
|
||||
is_passed=is_passed,
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ from quizzi.infrastructure.database.models import Test as TestModel
|
||||
from quizzi.infrastructure.database.models import TestAttempt as TestAttemptModel
|
||||
from quizzi.infrastructure.database.models import User as UserModel
|
||||
from quizzi.infrastructure.database.models import UserAnswer as UserAnswerModel
|
||||
from quizzi.infrastructure.utils.timezone import now_msk_naive
|
||||
from quizzi.infrastructure.utils.timezone import now_utc_naive
|
||||
|
||||
|
||||
@final
|
||||
@@ -135,7 +135,7 @@ class TestAttemptRepository:
|
||||
async def finish_attempt(self, attempt_id: int, score: int, is_passed: bool) -> TestAttempt | None:
|
||||
return await self.attempt_dao.update(
|
||||
attempt_id=attempt_id,
|
||||
finished_at=now_msk_naive(),
|
||||
finished_at=now_utc_naive(),
|
||||
score=score,
|
||||
is_passed=is_passed
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ from quizzi.infrastructure.database.dao.test import TestDAO
|
||||
from quizzi.infrastructure.database.dao.user_answer import UserAnswerDAO
|
||||
from quizzi.infrastructure.database.repo.test import TestRepository
|
||||
from quizzi.infrastructure.database.repo.test_attempt import TestAttemptRepository
|
||||
from quizzi.infrastructure.utils.timezone import now_msk_naive
|
||||
from quizzi.infrastructure.utils.timezone import now_utc_naive
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -18,7 +18,7 @@ async def deactivate_expired_tests(container: AsyncContainer) -> None:
|
||||
async with container() as request_container:
|
||||
test_dao = await request_container.get(TestDAO)
|
||||
|
||||
expired_tests = await test_dao.get_expired_active_tests(now_msk_naive())
|
||||
expired_tests = await test_dao.get_expired_active_tests(now_utc_naive())
|
||||
|
||||
for test in expired_tests:
|
||||
await test_dao.update(test.id, is_active=False)
|
||||
@@ -31,7 +31,7 @@ async def finish_expired_test_attempts(container: AsyncContainer, bot: Bot) -> N
|
||||
test_repo = await request_container.get(TestRepository)
|
||||
answer_dao = await request_container.get(UserAnswerDAO)
|
||||
|
||||
now = now_msk_naive()
|
||||
now = now_utc_naive()
|
||||
expired_attempts = await attempt_repo.get_expired_active_attempts(now)
|
||||
|
||||
for attempt, _ in expired_attempts:
|
||||
@@ -91,7 +91,7 @@ async def send_time_warning_notifications(container: AsyncContainer, bot: Bot) -
|
||||
attempt_repo = await request_container.get(TestAttemptRepository)
|
||||
test_repo = await request_container.get(TestRepository)
|
||||
|
||||
now = now_msk_naive()
|
||||
now = now_utc_naive()
|
||||
attempts_needing_warning = await attempt_repo.get_attempts_needing_warning(now)
|
||||
|
||||
for attempt, time_limit, questions_count in attempts_needing_warning:
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
MSK_TZ = ZoneInfo("Europe/Moscow")
|
||||
MSK_TZ = timezone(timedelta(hours=3))
|
||||
|
||||
|
||||
def now_msk() -> datetime:
|
||||
return datetime.now(MSK_TZ)
|
||||
|
||||
|
||||
def now_msk_naive() -> datetime:
|
||||
return datetime.now(MSK_TZ).replace(tzinfo=None)
|
||||
def now_utc() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
def now_utc_naive() -> datetime:
|
||||
return datetime.utcnow()
|
||||
return datetime.now(timezone.utc).replace(tzinfo=None)
|
||||
|
||||
|
||||
def to_msk(dt: datetime | None) -> datetime | None:
|
||||
if dt is None:
|
||||
return None
|
||||
if dt.tzinfo is None:
|
||||
return dt.replace(tzinfo=MSK_TZ)
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
return dt.astimezone(MSK_TZ)
|
||||
|
||||
@@ -8,7 +8,7 @@ from quizzi.infrastructure.database.repo.test import TestRepository
|
||||
from quizzi.infrastructure.database.repo.test_attempt import TestAttemptRepository
|
||||
from quizzi.infrastructure.utils.config import Config
|
||||
from quizzi.infrastructure.utils.test_id_to_hash import decode_id, encode_id
|
||||
from quizzi.infrastructure.utils.timezone import now_msk_naive
|
||||
from quizzi.infrastructure.utils.timezone import now_utc_naive
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -68,7 +68,7 @@ class TestService:
|
||||
if not test.is_active:
|
||||
return TestValidationResult(is_valid=False, error="❌ Тест деактивирован", test=test)
|
||||
|
||||
if test.expires_at and test.expires_at < now_msk_naive():
|
||||
if test.expires_at and test.expires_at < now_utc_naive():
|
||||
return TestValidationResult(is_valid=False, error="❌ Срок действия теста истек", test=test)
|
||||
|
||||
user = await self._user_dao.get_by_id(user_id)
|
||||
@@ -90,7 +90,7 @@ class TestService:
|
||||
if not test.is_active:
|
||||
return TestAccessResult(can_access=False, error="❌ Тест деактивирован")
|
||||
|
||||
if test.expires_at and test.expires_at < now_msk_naive():
|
||||
if test.expires_at and test.expires_at < now_utc_naive():
|
||||
return TestAccessResult(can_access=False, error="❌ Срок действия теста истек")
|
||||
|
||||
if test.attempts:
|
||||
|
||||
@@ -6,7 +6,7 @@ from quizzi.infrastructure.database.dao.test import TestDAO
|
||||
from quizzi.infrastructure.database.dao.user_answer import UserAnswerDAO
|
||||
from quizzi.infrastructure.database.repo.test import TestRepository
|
||||
from quizzi.infrastructure.database.repo.test_attempt import TestAttemptRepository
|
||||
from quizzi.infrastructure.utils.timezone import now_msk_naive
|
||||
from quizzi.infrastructure.utils.timezone import now_utc_naive
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -56,7 +56,7 @@ class TestAttemptService:
|
||||
return AttemptStartResult(success=False, error="❌ В тесте нет вопросов")
|
||||
|
||||
attempt = await self._attempt_repo.attempt_dao.create(user_id=user_id, test_id=test_id)
|
||||
started_at = now_msk_naive()
|
||||
started_at = now_utc_naive()
|
||||
|
||||
return AttemptStartResult(
|
||||
success=True,
|
||||
|
||||
Reference in New Issue
Block a user