From e16ba3580876d6064cf94a040ecd541958135842 Mon Sep 17 00:00:00 2001 From: seeleng Date: Tue, 24 Sep 2024 14:28:07 +0800 Subject: [PATCH 1/2] feat: add user read events table --- .../a31eae0cbe7a_add_user_read_event_table.py | 47 +++++++++++++++++++ backend/src/events/models.py | 13 +++++ 2 files changed, 60 insertions(+) create mode 100644 backend/alembic/versions/a31eae0cbe7a_add_user_read_event_table.py diff --git a/backend/alembic/versions/a31eae0cbe7a_add_user_read_event_table.py b/backend/alembic/versions/a31eae0cbe7a_add_user_read_event_table.py new file mode 100644 index 00000000..222c5c27 --- /dev/null +++ b/backend/alembic/versions/a31eae0cbe7a_add_user_read_event_table.py @@ -0,0 +1,47 @@ +"""Add user read event table + +Revision ID: a31eae0cbe7a +Revises: a4f85bdfef33 +Create Date: 2024-09-24 14:16:30.413513 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = "a31eae0cbe7a" +down_revision: Union[str, None] = "a4f85bdfef33" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "user_read_event", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("user_id", sa.Integer(), nullable=False), + sa.Column("event_id", sa.Integer(), nullable=False), + sa.Column("first_read", sa.DateTime(), nullable=False), + sa.Column("last_read", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint( + ["event_id"], + ["event.id"], + ), + sa.ForeignKeyConstraint( + ["user_id"], + ["user.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("user_read_event") + # ### end Alembic commands ### diff --git a/backend/src/events/models.py b/backend/src/events/models.py index 3aa89a81..40200cca 100644 --- a/backend/src/events/models.py +++ b/backend/src/events/models.py @@ -76,6 +76,19 @@ class Event(Base): backref="event", ) + reads: Mapped[list["UserReadEvent"]] = relationship(backref="user") + + +class UserReadEvent(Base): + __tablename__ = "user_read_event" + + id: Mapped[int] = mapped_column(primary_key=True) + + user_id: Mapped[int] = mapped_column(ForeignKey("user.id")) + event_id: Mapped[int] = mapped_column(ForeignKey("event.id")) + first_read: Mapped[datetime] + last_read: Mapped[datetime] + class Category(Base): __tablename__ = "category" From 13c7f4ed504431e3ccd2157ce8fa9f9b8f3530d9 Mon Sep 17 00:00:00 2001 From: seeleng Date: Tue, 24 Sep 2024 14:28:30 +0800 Subject: [PATCH 2/2] feat: add events/:id/read also update existing events routes to contain read information --- backend/src/events/dependencies.py | 5 ++++- backend/src/events/router.py | 35 ++++++++++++++++++++++++++++-- backend/src/events/schemas.py | 7 ++++++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/backend/src/events/dependencies.py b/backend/src/events/dependencies.py index 10465bc6..052594fb 100644 --- a/backend/src/events/dependencies.py +++ b/backend/src/events/dependencies.py @@ -2,17 +2,20 @@ from fastapi import Depends, HTTPException from sqlalchemy import select from sqlalchemy.orm import selectinload +from src.auth.dependencies import get_current_user from src.common.dependencies import get_session -from src.events.models import Analysis, Event, GPQuestion +from src.events.models import Analysis, Event, GPQuestion, UserReadEvent def retrieve_event( id: int, + user=Depends(get_current_user), session=Depends(get_session), ): event = session.scalar( select(Event) .where(Event.id == id) + .outerjoin(Event.reads.and_(UserReadEvent.user_id == user.id)) .options( selectinload( Event.gp_questions, diff --git a/backend/src/events/router.py b/backend/src/events/router.py index 6ccf0e9f..28dbb49f 100644 --- a/backend/src/events/router.py +++ b/backend/src/events/router.py @@ -6,7 +6,7 @@ from src.auth.dependencies import get_current_user from src.auth.models import User from src.events.dependencies import retrieve_event -from src.events.models import Article, Category, Event +from src.events.models import Article, Category, Event, UserReadEvent from src.common.dependencies import get_session from src.events.schemas import EventDTO, EventIndexResponse from src.notes.models import Note, NoteType @@ -18,7 +18,7 @@ @router.get("/") def get_events( - _: Annotated[User, Depends(get_current_user)], + user: Annotated[User, Depends(get_current_user)], start_date: Annotated[datetime | None, Query()] = None, end_date: Annotated[datetime | None, Query()] = None, session=Depends(get_session), @@ -36,6 +36,8 @@ def get_events( select(Event) .options(selectinload(Event.categories)) .options(selectinload(Event.original_article)) + .options(selectinload(Event.reads.and_(UserReadEvent.user_id == user.id))) + .outerjoin(Event.reads.and_(UserReadEvent.user_id == user.id)) .where(Event.id.in_(relevant_ids)) ) if limit is not None: @@ -53,6 +55,7 @@ def get_events( event_query = event_query.order_by(Event.rating.desc(), Event.date.desc()) events = list(session.scalars(event_query)) + print(events[0].reads) return EventIndexResponse(total_count=total_count, count=len(events), data=events) @@ -78,3 +81,31 @@ def get_event_notes( .where(Note.user_id == user.id) ) return notes + + +@router.post("/:id/read") +def read_event( + id: int, + user: Annotated[User, Depends(get_current_user)], + _=Depends(retrieve_event), + session=Depends(get_session), +): + read_event = session.scalars( + select(UserReadEvent) + .where(UserReadEvent.event_id == id) + .where(UserReadEvent.user_id == user.id) + ).first() + + if read_event: + read_event.last_read = datetime.now() + else: + date = datetime.now() + read_event = UserReadEvent( + event_id=id, + user_id=user.id, + first_read=date, + last_read=date, + ) + session.add(read_event) + session.commit() + return diff --git a/backend/src/events/schemas.py b/backend/src/events/schemas.py index 6a8f79c0..2da1b874 100644 --- a/backend/src/events/schemas.py +++ b/backend/src/events/schemas.py @@ -16,6 +16,12 @@ class ArticleDTO(BaseModel): image_url: str +class ReadDTO(BaseModel): + model_config = ConfigDict(from_attributes=True) + first_read: datetime + last_read: datetime + + class MiniEventDTO(BaseModel): model_config = ConfigDict(from_attributes=True) id: int @@ -26,6 +32,7 @@ class MiniEventDTO(BaseModel): categories: list[CategoryDTO] original_article: ArticleDTO + reads: list[ReadDTO] class AnalysisDTO(BaseModel):