Skip to content

Commit

Permalink
Merge branch 'develop' into add/user_status_check_in_decorated_handler
Browse files Browse the repository at this point in the history
  • Loading branch information
gorskyolga authored Aug 30, 2024
2 parents 36c077b + 3518cac commit 92e7cb3
Show file tree
Hide file tree
Showing 13 changed files with 219 additions and 19 deletions.
2 changes: 2 additions & 0 deletions src/api/endpoints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .health_check import health_check_router
from .notification import notification_router_by_admin, notification_router_by_token
from .tasks import task_read_router, task_response_router, task_write_router, tasks_router
from .tech_messages import tech_message_router
from .telegram_webhook import telegram_webhook_router
from .users import user_router

Expand All @@ -27,4 +28,5 @@
"admin_user_router",
"feedback_router",
"user_router",
"tech_message_router",
)
3 changes: 2 additions & 1 deletion src/api/endpoints/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from src.api.endpoints.analytics import analytic_router
from src.api.endpoints.notification import notification_router_by_admin
from src.api.endpoints.tech_messages import tech_message_router
from src.api.endpoints.users import user_router
from src.api.fastapi_admin_users import auth_backend, auth_cookie_backend, fastapi_admin_users
from src.api.permissions import is_active_user
Expand All @@ -18,7 +19,7 @@
admin_router.include_router(notification_router_by_admin, prefix="/messages", tags=["Messages"])
admin_router.include_router(user_router, prefix="/users", tags=["User"])
admin_router.include_router(admin_user_list_router, prefix="/admins", tags=["Admins"])

admin_router.include_router(tech_message_router, prefix="/tech_messages", tags=["Tech messages"])

admin_auth_router = APIRouter()
admin_auth_router.include_router(fastapi_admin_users.get_auth_router(auth_backend))
Expand Down
75 changes: 75 additions & 0 deletions src/api/endpoints/tech_messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from dependency_injector.wiring import Provide, inject
from fastapi import APIRouter, Depends, Query, Request, status

from src.api.pagination import TechMessagePaginator
from src.api.permissions import is_active_superuser
from src.api.schemas import TechMessagePaginateResponse, TechMessageRequest, TechMessageResponce
from src.core.depends import Container
from src.core.services import TechMessageService

tech_message_router = APIRouter()


@tech_message_router.get(
"",
response_model=TechMessagePaginateResponse,
response_model_exclude_none=True,
description="Получает список технических сообщений.",
)
@inject
async def get_all_tech_messages(
request: Request,
was_read: bool | None = None,
page: int = Query(default=1, ge=1),
limit: int = Query(default=20, ge=1),
tech_message_service: TechMessageService = Depends(Provide[Container.core_services_container.tech_message]),
tech_message_paginate: TechMessagePaginator = Depends(
Provide[Container.api_paginate_container.tech_message_paginate]
),
) -> TechMessagePaginateResponse:
filter_by = {"was_read": was_read}
tech_messages = await tech_message_service.get_filtered_tech_messages_by_page(filter_by, page, limit)
return await tech_message_paginate.paginate(tech_messages, page, limit, request.url.path, filter_by)


@tech_message_router.get(
"/{message_id}",
response_model=TechMessageResponce,
response_model_exclude_none=True,
description="Получает техническое сообщение.",
)
@inject
async def get_tech_message(
message_id: int,
tech_message_service: TechMessageService = Depends(Provide[Container.core_services_container.tech_message]),
) -> TechMessageResponce:
return await tech_message_service.get(message_id)


@tech_message_router.patch(
"/{message_id}",
response_model=TechMessageResponce,
response_model_exclude_none=True,
description="Обновляет данные технического сообщения.",
)
@inject
async def patch_tech_message(
message_id: int,
tech_message_request: TechMessageRequest,
tech_message_service: TechMessageService = Depends(Provide[Container.core_services_container.tech_message]),
) -> TechMessageResponce:
return await tech_message_service.partial_update(message_id, tech_message_request.model_dump())


@tech_message_router.delete(
"/{message_id}",
status_code=status.HTTP_204_NO_CONTENT,
description="Архивирует техническое сообщение.",
dependencies=[Depends(is_active_superuser)],
)
@inject
async def delete_tech_message(
message_id: int,
tech_message_service: TechMessageService = Depends(Provide[Container.core_services_container.tech_message]),
) -> None:
return await tech_message_service.archive(message_id)
11 changes: 9 additions & 2 deletions src/api/pagination.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import math
from typing import Any, Generic, TypeVar

from src.core.db.models import AdminUser, User
from src.core.db.repository import AbstractRepository, AdminUserRepository, UserRepository
from src.core.db.models import AdminUser, TechMessage, User
from src.core.db.repository import AbstractRepository, AdminUserRepository, TechMessageRepository, UserRepository
from src.core.db.repository.base import FilterableRepository

DatabaseModel = TypeVar("DatabaseModel")
Expand Down Expand Up @@ -67,3 +67,10 @@ class UserPaginator(FilterablePaginator[User]):

def __init__(self, user_repository: UserRepository) -> None:
super().__init__(user_repository)


class TechMessagePaginator(FilterablePaginator[TechMessage]):
"""Класс для пагинации и фильтрации данных из модели TechMessage."""

def __init__(self, repository: TechMessageRepository) -> None:
super().__init__(repository)
4 changes: 4 additions & 0 deletions src/api/schemas/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
TelegramNotificationUsersRequest,
)
from .tasks import TaskRequest, TaskResponse, TasksRequest, UserResponseToTaskRequest
from .tech_messages import TechMessagePaginateResponse, TechMessageRequest, TechMessageResponce
from .token_schemas import TokenCheckResponse
from .users import UserResponse, UsersPaginatedResponse

Expand Down Expand Up @@ -59,4 +60,7 @@
"UserResponse",
"UserResponseToTaskRequest",
"UsersPaginatedResponse",
"TechMessageResponce",
"TechMessageRequest",
"TechMessagePaginateResponse",
)
24 changes: 24 additions & 0 deletions src/api/schemas/tech_messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from datetime import datetime

from src.api.schemas.base import PaginateBase, RequestBase, ResponseBase


class TechMessageResponce(ResponseBase):
"""Класс схемы ответа."""

id: int
text: str
was_read: bool
created_at: datetime


class TechMessageRequest(RequestBase):
"""Класс схемы запроса."""

was_read: bool


class TechMessagePaginateResponse(PaginateBase):
"""Класс схемы постраничного ответа."""

result: list[TechMessageResponce] | None
28 changes: 16 additions & 12 deletions src/bot/handlers/categories.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@
from src.core.logging.utils import logger_decor
from src.core.services.procharity_api import ProcharityAPI

text_chose_category = (
"Чтобы мне было понятнее, с какими задачами ты готов помогать фондам, "
"отметь свои профессиональные компетенции (можно выбрать несколько). "
'После этого нажми "Готово 👌"'
)

text_chose_subcategory = (
"Чтобы мне было понятнее, с какими задачами ты готов помогать фондам, "
"отметь свои профессиональные компетенции (можно выбрать несколько). "
'После этого нажми "Назад ⬅️"'
)


@logger_decor
@registered_user_required
Expand All @@ -34,9 +46,7 @@ async def categories_callback(
selected_categories_with_parents = await user_service.get_user_categories_with_parents(update.effective_user.id)
await context.bot.send_message(
chat_id=update.effective_chat.id,
text="Чтобы я знал, с какими задачами ты готов помогать, "
"выбери свои профессиональные компетенции (можно выбрать "
'несколько). После этого, нажми на пункт "Готово 👌"',
text=text_chose_category,
reply_markup=await get_checked_categories_keyboard(categories, selected_categories_with_parents),
)

Expand Down Expand Up @@ -123,9 +133,7 @@ async def subcategories_callback(
selected_categories = await user_service.get_user_categories(update.effective_user.id)

await query.message.edit_text(
"Чтобы я знал, с какими задачами ты готов помогать, "
"выбери свои профессиональные компетенции (можно выбрать "
'несколько). После этого, нажми на пункт "Готово 👌"',
text_chose_subcategory,
reply_markup=await get_subcategories_keyboard(parent_id, subcategories, selected_categories),
)

Expand Down Expand Up @@ -158,9 +166,7 @@ async def select_subcategory_callback(
parent_id = context.user_data["parent_id"]
subcategories = await category_service.get_unarchived_subcategories(parent_id)
await query.message.edit_text(
"Чтобы я знал, с какими задачами ты готов помогать, "
"выбери свои профессиональные компетенции (можно выбрать "
'несколько). После этого, нажми на пункт "Готово 👌"',
text_chose_subcategory,
reply_markup=await get_subcategories_keyboard(parent_id, subcategories, selected_categories),
)

Expand All @@ -179,9 +185,7 @@ async def back_subcategory_callback(
selected_categories_with_parents = await user_service.get_user_categories_with_parents(update.effective_user.id)

await query.message.edit_text(
"Чтобы я знал, с какими задачами ты готов помогать, "
"выбери свои профессиональные компетенции (можно выбрать "
'несколько). После этого, нажми на пункт "Готово 👌"',
text_chose_category,
reply_markup=await get_checked_categories_keyboard(categories, selected_categories_with_parents),
)

Expand Down
14 changes: 11 additions & 3 deletions src/bot/handlers/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,19 @@ async def on_chat_member_update(
if my_chat_member.new_chat_member.status == my_chat_member.new_chat_member.BANNED:
await user_service.bot_banned(user)
await procharity_api.send_user_bot_status(user)
return user
if my_chat_member.new_chat_member.status == my_chat_member.new_chat_member.MEMBER:
elif my_chat_member.new_chat_member.status == my_chat_member.new_chat_member.MEMBER:
await user_service.bot_unbanned(user)
await procharity_api.send_user_bot_status(user)
return user
unblock_text = (
"<b>Ты разблокировал бот ProCharity</b>" if user.is_volunteer else "<b>Вы разблокировали бот ProCharity</b>"
)
await context.bot.send_message(
chat_id=effective_user.id,
text=unblock_text,
parse_mode=ParseMode.HTML,
disable_web_page_preview=True,
)
return user


def registration_handlers(app: Application):
Expand Down
7 changes: 7 additions & 0 deletions src/core/db/repository/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@ async def get(self, id: int, *, is_archived: bool | None = False) -> DatabaseMod
raise NotFoundException(object_name=self._model.__name__, object_id=id)
return db_obj

async def archive(self, id: int) -> DatabaseModel:
"""Архивирует объект модели"""
db_obj = await self.get(id)
db_obj.is_archived = True
db_obj = await self.update(id, db_obj)
return db_obj


class ContentRepository(ArchivableRepository):
"""Абстрактный класс, для контента."""
Expand Down
1 change: 1 addition & 0 deletions src/core/db/repository/category.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ async def get_unarchived_parents_with_children_count(self):
select(Category.name, Category.id, parent_and_children_count_subquery.c.children_count)
.select_from(Category)
.join(parent_and_children_count_subquery, Category.id == parent_and_children_count_subquery.c.parent_id)
.order_by(Category.name)
)
return parents_with_children_count.all()

Expand Down
46 changes: 46 additions & 0 deletions src/core/db/repository/tech_message.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from collections.abc import Sequence
from typing import Any

from sqlalchemy import Select, desc, false, func, select, true
from sqlalchemy.ext.asyncio import AsyncSession

from src.core.db.models import TechMessage
Expand All @@ -9,3 +13,45 @@ class TechMessageRepository(ArchivableRepository):

def __init__(self, session: AsyncSession) -> None:
super().__init__(session, TechMessage)

async def partial_update(self, id: int, data: dict) -> TechMessage:
"""Обновляет данные/часть данных технического сообщения."""
tech_message = await self.get(id)

for attr, value in data.items():
setattr(tech_message, attr, value)

return await self.update(id, tech_message)

def _add_filter_by_was_read(self, statement: Select, was_read: bool | None) -> Select:
"""Добавляет к оператору SELECT проверку статуса технического сообщения (was_read)."""
if was_read is True:
return statement.where(TechMessage.was_read == true())
elif was_read is False:
return statement.where(TechMessage.was_read == false())
return statement

async def count_by_filter(self, filter_by: dict) -> int:
"""Возвращает количество не архивных данных, удовлетворяющих фильтру."""
statement = self._add_filter_by_was_read(
select(func.count()).select_from(TechMessage),
filter_by.get("was_read"),
)
return await self._session.scalar(statement.where(TechMessage.is_archived == false()))

async def get_filtered_tech_messages_by_page(
self, filter_by: dict[str:Any], page: int, limit: int, column_name: str = "created_at"
) -> Sequence[TechMessage]:
"""
Получает отфильтрованные не архивные данные, ограниченные параметрами page и limit
и отсортированные по полю column_name в порядке убывания.
"""
offset = (page - 1) * limit
statement = self._add_filter_by_was_read(
select(TechMessage),
filter_by.get("was_read"),
)
objects = await self._session.scalars(
statement.where(TechMessage.is_archived == false()).limit(limit).offset(offset).order_by(desc(column_name))
)
return objects.all()
7 changes: 6 additions & 1 deletion src/core/depends/pagination.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from dependency_injector import containers, providers

from src.api.pagination import AdminUserPaginator, UserPaginator
from src.api.pagination import AdminUserPaginator, TechMessagePaginator, UserPaginator


class PaginateContainer(containers.DeclarativeContainer):
Expand All @@ -17,3 +17,8 @@ class PaginateContainer(containers.DeclarativeContainer):
AdminUserPaginator,
repository=repositories.admin_repository,
)

tech_message_paginate = providers.Factory(
TechMessagePaginator,
repository=repositories.tech_message_repository,
)
16 changes: 16 additions & 0 deletions src/core/services/tech_message.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any

from src.core.db.models import TechMessage
from src.core.db.repository import TechMessageRepository

Expand All @@ -10,3 +12,17 @@ def __init__(self, repository: TechMessageRepository):

async def create(self, message: str) -> TechMessage:
return await self._repository.create(TechMessage(text=message))

async def get_filtered_tech_messages_by_page(
self, filter_by: dict[str:Any], page: int, limit: int
) -> list[TechMessage]:
return await self._repository.get_filtered_tech_messages_by_page(filter_by, page, limit)

async def get(self, id: int) -> TechMessage:
return await self._repository.get(id)

async def partial_update(self, id: int, data: dict) -> TechMessage:
return await self._repository.partial_update(id, data)

async def archive(self, id: int) -> None:
await self._repository.archive(id)

0 comments on commit 92e7cb3

Please sign in to comment.