Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added app/core/feed/__init__.py
Empty file.
55 changes: 55 additions & 0 deletions app/core/feed/cruds_feed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from collections.abc import Sequence
from uuid import UUID

from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.feed import models_feed
from app.core.feed.types_feed import NewsStatus


async def create_news(
news: models_feed.News,
db: AsyncSession,
) -> None:
"""
Create a news
"""

db.add(news)


async def get_news(
status: list[NewsStatus],
db: AsyncSession,
) -> Sequence[models_feed.News]:
result = await db.execute(
select(models_feed.News).where(
models_feed.News.status.in_(status),
),
)
return result.scalars().all()


async def get_news_by_id(
news_id: UUID,
db: AsyncSession,
) -> models_feed.News | None:
result = await db.execute(
select(models_feed.News).where(
models_feed.News.id == news_id,
),
)
return result.scalars().first()


async def change_news_status(
news_id: UUID,
status: NewsStatus,
db: AsyncSession,
) -> None:
await db.execute(
update(models_feed.News)
.where(models_feed.News.id == news_id)
.values(status=status),
)
135 changes: 135 additions & 0 deletions app/core/feed/endpoints_feed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import logging
from uuid import UUID

from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import FileResponse
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.feed import cruds_feed, schemas_feed
from app.core.feed.types_feed import NewsStatus
from app.core.groups.groups_type import GroupType
from app.core.users import models_users
from app.dependencies import (
get_db,
is_user_a_school_member,
is_user_in,
)
from app.types.module import CoreModule
from app.utils.tools import get_file_from_data

router = APIRouter(tags=["Feed"])

core_module = CoreModule(
root="feed",
tag="Feed",
router=router,
factory=None,
)

hyperion_error_logger = logging.getLogger("hyperion.error")


@router.get(
"/feed/news",
response_model=list[schemas_feed.News],
status_code=200,
)
async def get_published_news(
db: AsyncSession = Depends(get_db),
user: models_users.CoreUser = Depends(is_user_a_school_member),
):
"""
Return published news from the feed
"""

return await cruds_feed.get_news(status=[NewsStatus.PUBLISHED], db=db)


@router.get(
"/feed/news/{news_id}/image",
response_class=FileResponse,
status_code=200,
)
async def get_news_image(
news_id: UUID,
db: AsyncSession = Depends(get_db),
user: models_users.CoreUser = Depends(is_user_a_school_member),
):
"""
Return the image of a news
"""

news = await cruds_feed.get_news_by_id(news_id=news_id, db=db)
if news is None:
raise HTTPException(
status_code=404,
detail="The news does not exist",
)

return get_file_from_data(
directory=news.image_directory,
filename=news.image_id,
)


@router.get(
"/feed/admin/news",
response_model=list[schemas_feed.News],
status_code=200,
)
async def get_admin_news(
status: list[NewsStatus],
db: AsyncSession = Depends(get_db),
user: models_users.CoreUser = Depends(is_user_in(GroupType.feed_admin)),
):
"""
Return news from the feed

**This endpoint is only usable by feed administrators**
"""

return await cruds_feed.get_news(status=status, db=db)


@router.post(
"/feed/admin/news/{news_id}/approve",
status_code=204,
)
async def approve_news(
news_id: UUID,
db: AsyncSession = Depends(get_db),
user: models_users.CoreUser = Depends(is_user_in(GroupType.feed_admin)),
):
"""
Approve a news

**This endpoint is only usable by feed administrators**
"""

return await cruds_feed.change_news_status(
news_id=news_id,
status=NewsStatus.PUBLISHED,
db=db,
)


@router.post(
"/feed/admin/news/{news_id}/reject",
status_code=204,
)
async def reject_news(
news_id: UUID,
db: AsyncSession = Depends(get_db),
user: models_users.CoreUser = Depends(is_user_in(GroupType.feed_admin)),
):
"""
Reject a news

**This endpoint is only usable by feed administrators**
"""

await cruds_feed.change_news_status(
news_id=news_id,
status=NewsStatus.REJECTED,
db=db,
)
36 changes: 36 additions & 0 deletions app/core/feed/models_feed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from datetime import datetime
from uuid import UUID

from sqlalchemy.orm import Mapped

from app.core.feed.types_feed import NewsStatus
from app.types.sqlalchemy import Base, PrimaryKey


class News(Base):
__tablename__ = "feed_news"

id: Mapped[PrimaryKey]
title: Mapped[str]

start: Mapped[datetime]
end: Mapped[datetime | None]

# Name of the entity that created the news
entity: Mapped[str]

# The news may be related to a specific location
location: Mapped[str | None]

# The news may be related to a specific action
# If so, the action button should be displayed at this datetime
action_start: Mapped[datetime | None]

module: Mapped[str]
# UUID of the related object in the module database
module_object_id: Mapped[UUID]

image_directory: Mapped[str]
image_id: Mapped[UUID]

status: Mapped[NewsStatus]
35 changes: 35 additions & 0 deletions app/core/feed/schemas_feed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from datetime import datetime
from uuid import UUID

from pydantic import BaseModel, Field

from app.core.feed.types_feed import NewsStatus


class News(BaseModel):
id: UUID
title: str

start: datetime
end: datetime | None

entity: str = Field(description="Name of the entity that created the news")

location: str | None = Field(
description="The news may be related to a specific location",
)

action_start: datetime | None = Field(
description="The news may be related to a specific action. If so, the action button should be displayed at this datetime",
)

module: str
# UUID of the related object in the module database
module_object_id: UUID

status: NewsStatus


class NewsComplete(News):
image_directory: str
image_id: UUID
7 changes: 7 additions & 0 deletions app/core/feed/types_feed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from enum import Enum


class NewsStatus(str, Enum):
WAITING_APPROVAL = "waiting_approval"
REJECTED = "rejected"
PUBLISHED = "published"
69 changes: 69 additions & 0 deletions app/core/feed/utils_feed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import uuid
from datetime import datetime

from sqlalchemy.ext.asyncio import AsyncSession

from app.core.feed import cruds_feed, models_feed
from app.core.feed.types_feed import NewsStatus
from app.core.groups.groups_type import GroupType
from app.core.notification.schemas_notification import Message
from app.utils.communication.notifications import NotificationTool


async def create_feed_news(
title: str,
start: datetime,
end: datetime | None,
entity: str,
location: str | None,
action_start: datetime | None,
module: str,
module_object_id: uuid.UUID,
image_directory: str,
image_id: uuid.UUID,
require_feed_admin_approval: bool,
db: AsyncSession,
notification_tool: NotificationTool,
):
"""
Create a news in the feed

title: title of the news,
start: datetime corresponding to the start of the news. The news should be visible at this day
end: optional end datetime, may be used to compute the news subtitle
entity: name of the entity that created the news, usually the name of an association or a group
module: identifier of the module that created the news, may be used to open the right page in the app
module_object_id: identifier of the object that is linked to the news in the module, may be used to open the right page in the app
image_directory: folder where the image is stored, used to display the image in the news
image_id: uuid of the image is stored, used to display the image in the news,
require_feed_admin_approval: if the news can be published directly or if it requires approval from a feed administrator
"""

news = models_feed.News(
id=uuid.uuid4(),
title=title,
start=start,
end=end,
entity=entity,
location=location,
action_start=action_start,
module=module,
module_object_id=module_object_id,
image_directory=image_directory,
image_id=image_id,
status=NewsStatus.WAITING_APPROVAL
if require_feed_admin_approval
else NewsStatus.PUBLISHED,
)
await cruds_feed.create_news(news=news, db=db)

if require_feed_admin_approval:
message = Message(
title="🔔 Feed - a news require approval",
content=f"{entity} has created {title}",
action_module="feed",
)
await notification_tool.send_notification_to_group(
group_id=GroupType.feed_admin,
message=message,
)
Loading