Skip to content

Commit

Permalink
Бэк для оповещений. (#101)
Browse files Browse the repository at this point in the history
  • Loading branch information
CuriousGecko authored Aug 4, 2024
1 parent 070a2d1 commit 6d3aa25
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 23 deletions.
44 changes: 44 additions & 0 deletions alembic/versions/20d3addee5ba_notification_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""notification_models
Revision ID: 20d3addee5ba
Revises: 981474c39706
Create Date: 2024-07-30 20:18:41.017469
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '20d3addee5ba'
down_revision = '981474c39706'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('notification', schema=None) as batch_op:
batch_op.add_column(sa.Column('message', sa.Text(), nullable=False))
batch_op.drop_column('description')

with op.batch_alter_table('notification_user_association', schema=None) as batch_op:
batch_op.add_column(sa.Column('viewed', sa.Boolean(), nullable=True))
batch_op.add_column(sa.Column('date', sa.DateTime(timezone=True), nullable=True))
batch_op.drop_constraint('constraint_notification_user', type_='unique')

# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('notification_user_association', schema=None) as batch_op:
batch_op.create_unique_constraint('constraint_notification_user', ['notification_id', 'user_id'])
batch_op.drop_column('date')
batch_op.drop_column('viewed')

with op.batch_alter_table('notification', schema=None) as batch_op:
batch_op.add_column(sa.Column('description', sa.TEXT(), nullable=True))
batch_op.drop_column('message')

# ### end Alembic commands ###
146 changes: 137 additions & 9 deletions app/api/endpoints/notification.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,156 @@
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, Query, Response
from sqlalchemy.ext.asyncio import AsyncSession
from starlette import status

from app.api.validators import check_obj_duplicate, check_obj_exists
from app.core.db import get_async_session
from app.core.user import current_superuser, current_user
from app.crud import notification_crud
from app.schemas.notification import NotificationCreate, NotificationRead
from app.crud import notification_crud, user_crud
from app.models import User
from app.schemas.notification import (
AddNotificationForUser, NotificationCreate, NotificationRead,
ReadNotificationForUser,
)
from app.services.utils import (
Pagination, add_response_headers, get_pagination_params, paginated,
)

router = APIRouter()


@router.post('/', dependencies=[Depends(current_superuser)])
@router.post(
'/',
status_code=status.HTTP_201_CREATED,
dependencies=[Depends(current_superuser)],
)
async def create_notification(
notification: NotificationCreate,
session: AsyncSession = Depends(get_async_session)
session: AsyncSession = Depends(get_async_session)
):
return await notification_crud.create(notification, session)
"""Создаст уведомление."""
obj = await notification_crud.get_by_attr(
attr_name='name',
attr_value=notification.name,
session=session,
)
await check_obj_duplicate(obj=obj)
await notification_crud.create(obj_in=notification, session=session)


@router.get(
'/',
response_model=list[NotificationRead],
dependencies=[Depends(current_user)]
dependencies=[Depends(current_superuser)],
)
async def get_all_notifications(
session: AsyncSession = Depends(get_async_session)
session: AsyncSession = Depends(get_async_session),
):
"""Вернет список всех уведомлений."""
return await notification_crud.get_multi(session=session)


@router.delete(
'/{notification_id}',
status_code=status.HTTP_204_NO_CONTENT,
dependencies=[Depends(current_superuser)],
)
async def delete_notification(
notification_id: int,
session: AsyncSession = Depends(get_async_session)
):
"""Удалит уведомление."""
obj = await notification_crud.get_by_attr(
attr_name='id',
attr_value=notification_id,
session=session,
)
await check_obj_exists(obj=obj)
await notification_crud.remove(
db_obj=obj,
session=session,
)


@router.post(
'/add-for-user',
status_code=status.HTTP_201_CREATED,
dependencies=[Depends(current_superuser)],
)
async def add_notification_for_user(
notification: AddNotificationForUser,
session: AsyncSession = Depends(get_async_session)
):
"""Создаст связь пользователя и уведомления."""
db_user = await user_crud.get_by_attr(
attr_name='id',
attr_value=notification.user_id,
session=session,
)
await check_obj_exists(db_user)
db_notification = await notification_crud.get_by_attr(
attr_name='id',
attr_value=notification.notification_id,
session=session,
)
await check_obj_exists(db_notification)

await notification_crud.add_notification_for_user(
obj_in=notification,
session=session,
)


@router.get(
'/get-for-user',
response_model=list[ReadNotificationForUser],
response_model_exclude_none=True,
dependencies=[Depends(current_user)],
)
async def get_notifications_for_user(
response: Response,
pagination: Pagination = Depends(get_pagination_params),
user: User = Depends(current_user),
viewed: bool = Query(default=False, alias="viewed"),
bell: bool = Query(default=False, alias="bell"),
session: AsyncSession = Depends(get_async_session),
):
"""
Вернет список уведомлений для пользователя.
Параметры:
- **viewed**: (bool) Если True, вернет прочитанные уведомления.
По умолчанию False.
- **bell**: (bool) Если True, вернет непрочитанные уведомления без поля
message. Дополнительно указывать параметр viewed не требуется.
По умолчанию False.
"""
notifications = await notification_crud.get_user_notifications(
user_id=user.id,
viewed=viewed,
bell=bell,
session=session,
)
add_response_headers(response, notifications, pagination)
return paginated(notifications, pagination)


@router.patch(
'/mark-as-viewed/{user_notification_id}',
dependencies=[Depends(current_user)],
)
async def mark_as_viewed(
user_notification_id: int,
user: User = Depends(current_user),
session: AsyncSession = Depends(get_async_session)
):
return await notification_crud.get_multi(session)
"""Пометит уведомление как прочитанное."""
obj = await notification_crud.get_user_notification_by_id(
obj_id=user_notification_id,
user_id=user.id,
session=session,
)
await check_obj_exists(obj=obj)
await notification_crud.mark_user_notification_as_viewed(
obj_id=user_notification_id,
session=session,
)
6 changes: 5 additions & 1 deletion app/api/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

from app.api.endpoints import (
achievement_router, course_router, examination_router, group_router,
locale_router, profile_router, tariff_router, task_router, user_router,
locale_router, notification_router, profile_router, tariff_router,
task_router, user_router,
)

main_router = APIRouter()
Expand All @@ -24,3 +25,6 @@
main_router.include_router(
locale_router, prefix='/locales', tags=['Locales']
)
main_router.include_router(
notification_router, prefix='/notifications', tags=['Notifications']
)
71 changes: 70 additions & 1 deletion app/crud/notification.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,78 @@
from sqlalchemy import and_, select, update
from sqlalchemy.ext.asyncio import AsyncSession

from app.crud.base import CRUDBase
from app.models import Notification
from app.models.notification import notification_user_association


class CRUDNotification(CRUDBase):
pass
async def add_notification_for_user(
self,
obj_in,
session: AsyncSession,
):
stmt = notification_user_association.insert().values(
user_id=obj_in.user_id,
notification_id=obj_in.notification_id,
)
await session.execute(stmt)
await session.commit()

async def get_user_notifications(
self,
user_id: int,
viewed: bool,
bell: bool,
session: AsyncSession,
):
stmt = select(
self.model.name,
self.model.message if not bell else None,
notification_user_association.c.id,
notification_user_association.c.date,
).join(
notification_user_association,
).where(and_(
notification_user_association.c.user_id == user_id,
notification_user_association.c.viewed == (
viewed if not bell else False
),
))
db_objs = await session.execute(stmt)
return db_objs.mappings().all()

async def get_user_notification_by_id(
self,
obj_id: int,
user_id: int,
session: AsyncSession,
):
stmt = select(
notification_user_association,
).where(and_(
notification_user_association.c.id == obj_id,
notification_user_association.c.user_id == user_id,
))
db_obj = await session.execute(stmt)
return db_obj.mappings().all()

async def mark_user_notification_as_viewed(self, obj_id, session):
stmt = update(
notification_user_association,
).where(
notification_user_association.c.id == obj_id,
).values(viewed=True)
await session.execute(stmt)
await session.commit()

async def remove(self, db_obj: Notification, session: AsyncSession):
stmt = notification_user_association.delete().where(
notification_user_association.c.notification_id == db_obj.id
)
await session.execute(stmt)
await session.delete(db_obj)
await session.commit()


notification_crud = CRUDNotification(Notification)
17 changes: 9 additions & 8 deletions app/models/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import TYPE_CHECKING

from sqlalchemy import (
Column, ForeignKey, Integer, String, Table, Text, UniqueConstraint,
Boolean, Column, DateTime, ForeignKey, Integer, String, Table, Text, func,
)
from sqlalchemy.orm import Mapped, relationship

Expand All @@ -13,26 +13,27 @@
if TYPE_CHECKING:
from app.models import User


notification_user_association = Table(
'notification_user_association',
Base.metadata,
Column('id', Integer, primary_key=True),
Column('notification_id', ForeignKey('notification.id')),
Column('user_id', ForeignKey('user.id')),
UniqueConstraint(
'notification_id', 'user_id', name='constraint_notification_user'
),
Column('viewed', Boolean, default=False),
Column('date', DateTime, default=func.now()),
)


class Notification(Base):
name: str = Column(
String(length=settings.max_length_string), unique=True, nullable=False
String(length=settings.max_length_string),
unique=True,
nullable=False,
)
description: str = Column(Text)
message: str = Column(Text, nullable=False)
users: Mapped[list[User]] = relationship(
secondary=notification_user_association, back_populates='notifications'
secondary=notification_user_association,
back_populates='notifications',
)

def __repr__(self):
Expand Down
20 changes: 16 additions & 4 deletions app/schemas/notification.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
from typing import Optional
from datetime import datetime

from pydantic import BaseModel


class NotificationCreate(BaseModel):
name: str
description: Optional[str]
message: str


class NotificationRead(BaseModel):
id: int
name: Optional[str]
description: Optional[str]
name: str
message: str


class AddNotificationForUser(BaseModel):
user_id: int
notification_id: int


class ReadNotificationForUser(BaseModel):
id: int
name: str
message: str = None
date: datetime

class Config:
from_attributes = True

0 comments on commit 6d3aa25

Please sign in to comment.