diff --git a/backend/alembic/versions/37cf2356fe08_move_table_back.py b/backend/alembic/versions/37cf2356fe08_move_table_back.py new file mode 100644 index 00000000..c95b8bda --- /dev/null +++ b/backend/alembic/versions/37cf2356fe08_move_table_back.py @@ -0,0 +1,61 @@ +"""move table back + +Revision ID: 37cf2356fe08 +Revises: c3b7f848f6f3 +Create Date: 2024-09-23 01:20:37.025966 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = "37cf2356fe08" +down_revision: Union[str, None] = "c3b7f848f6f3" +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( + "analysis", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("event_id", sa.Integer(), nullable=False), + sa.Column("category_id", sa.Integer(), nullable=False), + sa.Column("content", sa.String(), nullable=False), + sa.ForeignKeyConstraint( + ["category_id"], + ["category.id"], + ), + sa.ForeignKeyConstraint( + ["event_id"], + ["event.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.drop_table("analysis1") + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "analysis1", + sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column("event_id", sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column("category_id", sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column("content", sa.VARCHAR(), autoincrement=False, nullable=False), + sa.ForeignKeyConstraint( + ["category_id"], ["category.id"], name="analysis1_category_id_fkey" + ), + sa.ForeignKeyConstraint( + ["event_id"], ["event.id"], name="analysis1_event_id_fkey" + ), + sa.PrimaryKeyConstraint("id", name="analysis1_pkey"), + ) + op.drop_table("analysis") + # ### end Alembic commands ### diff --git a/backend/alembic/versions/90baf2b50ff3_add_id_to_analysis.py b/backend/alembic/versions/90baf2b50ff3_add_id_to_analysis.py new file mode 100644 index 00000000..aa46aee4 --- /dev/null +++ b/backend/alembic/versions/90baf2b50ff3_add_id_to_analysis.py @@ -0,0 +1,31 @@ +"""Add id to analysis + +Revision ID: 90baf2b50ff3 +Revises: bc5fcbe47713 +Create Date: 2024-09-23 00:55:33.251027 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = "90baf2b50ff3" +down_revision: Union[str, None] = "bc5fcbe47713" +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.add_column("analysis", sa.Column("id", sa.Integer(), nullable=False)),) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("analysis", "id") + # ### end Alembic commands ### diff --git a/backend/alembic/versions/c3b7f848f6f3_move_table_temporarily.py b/backend/alembic/versions/c3b7f848f6f3_move_table_temporarily.py new file mode 100644 index 00000000..c6578d8b --- /dev/null +++ b/backend/alembic/versions/c3b7f848f6f3_move_table_temporarily.py @@ -0,0 +1,61 @@ +"""move table temporarily + +Revision ID: c3b7f848f6f3 +Revises: ddf34f03e551 +Create Date: 2024-09-23 01:20:21.380233 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = "c3b7f848f6f3" +down_revision: Union[str, None] = "ddf34f03e551" +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( + "analysis1", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("event_id", sa.Integer(), nullable=False), + sa.Column("category_id", sa.Integer(), nullable=False), + sa.Column("content", sa.String(), nullable=False), + sa.ForeignKeyConstraint( + ["category_id"], + ["category.id"], + ), + sa.ForeignKeyConstraint( + ["event_id"], + ["event.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.drop_table("analysis") + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "analysis", + sa.Column("event_id", sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column("category_id", sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column("content", sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column("id", sa.INTEGER(), autoincrement=False, nullable=False), + sa.ForeignKeyConstraint( + ["category_id"], ["category.id"], name="analysis_category_id_fkey" + ), + sa.ForeignKeyConstraint( + ["event_id"], ["event.id"], name="analysis_event_id_fkey" + ), + sa.PrimaryKeyConstraint("id", name="analysis_pkey"), + ) + op.drop_table("analysis1") + # ### end Alembic commands ### diff --git a/backend/alembic/versions/ddf34f03e551_make_id_in_analysis_actually_pk.py b/backend/alembic/versions/ddf34f03e551_make_id_in_analysis_actually_pk.py new file mode 100644 index 00000000..9fbadb05 --- /dev/null +++ b/backend/alembic/versions/ddf34f03e551_make_id_in_analysis_actually_pk.py @@ -0,0 +1,31 @@ +"""make id in analysis actually pk + +Revision ID: ddf34f03e551 +Revises: 90baf2b50ff3 +Create Date: 2024-09-23 01:08:01.468695 + +""" + +from typing import Sequence, Union + +from alembic import op + + +# revision identifiers, used by Alembic. +revision: str = "ddf34f03e551" +down_revision: Union[str, None] = "90baf2b50ff3" +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.execute("ALTER TABLE analysis DROP CONSTRAINT analysis_pkey CASCADE") + op.create_primary_key("analysis_pkey", "analysis", ["id"]) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/backend/src/events/models.py b/backend/src/events/models.py index 6209b518..3aa89a81 100644 --- a/backend/src/events/models.py +++ b/backend/src/events/models.py @@ -62,11 +62,13 @@ class Event(Base): back_populates="events", secondary="analysis" ) + analysises: Mapped[list["Analysis"]] = relationship(back_populates="event") + original_article: Mapped[Article] = relationship(back_populates="original_events") articles: Mapped[list[Article]] = relationship( back_populates="events", secondary=article_event_table ) - gp_questions: Mapped["GPQuestion"] = relationship(back_populates="event") + gp_questions: Mapped[list["GPQuestion"]] = relationship(back_populates="event") notes = relationship( "Note", @@ -84,17 +86,21 @@ class Category(Base): events: Mapped[list[Event]] = relationship( secondary="analysis", back_populates="categories" ) + analysises: Mapped[list["Analysis"]] = relationship(back_populates="category") class Analysis(Base): __tablename__ = "analysis" - event_id: Mapped[int] = mapped_column(ForeignKey("event.id"), primary_key=True) - category_id: Mapped[int] = mapped_column( - ForeignKey("category.id"), primary_key=True - ) + id: Mapped[int] = mapped_column(primary_key=True) + + event_id: Mapped[int] = mapped_column(ForeignKey("event.id")) + category_id: Mapped[int] = mapped_column(ForeignKey("category.id")) content: Mapped[str] + event: Mapped[Event] = relationship(back_populates="analysises") + category: Mapped[Category] = relationship(back_populates="analysises") + class GPQuestion(Base): __tablename__ = "gp_question" diff --git a/backend/src/events/router.py b/backend/src/events/router.py index 823773e3..eab81efa 100644 --- a/backend/src/events/router.py +++ b/backend/src/events/router.py @@ -1,10 +1,12 @@ +from datetime import datetime +from http import HTTPStatus from typing import Annotated -from fastapi import APIRouter, Depends, Query +from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy import select from sqlalchemy.orm import selectinload from src.auth.dependencies import get_current_user from src.auth.models import User -from src.events.models import Category, Event +from src.events.models import Analysis, Article, Category, Event, GPQuestion from src.common.dependencies import get_session from src.events.schemas import EventDTO, EventIndexResponse @@ -15,6 +17,8 @@ @router.get("/") def get_events( _: Annotated[User, Depends(get_current_user)], + start_date: Annotated[datetime | None, Query()] = None, + end_date: Annotated[datetime | None, Query()] = None, session=Depends(get_session), category_ids: Annotated[list[int] | None, Query()] = None, limit: int | None = None, @@ -35,6 +39,15 @@ def get_events( event_query = event_query.limit(limit) if offset is not None: event_query = event_query.offset(offset) + if start_date is not None: + event_query = event_query.where( + Event.original_article.has(Article.date >= start_date) + ) + if end_date is not None: + event_query = event_query.where( + Event.original_article.has(Article.date <= end_date) + ) + event_query = event_query.order_by(Event.rating.desc(), Event.date.desc()) events = list(session.scalars(event_query)) return EventIndexResponse(total_count=total_count, count=len(events), data=events) @@ -47,7 +60,20 @@ def get_event( session=Depends(get_session), ) -> EventDTO: event = session.scalar( - select(Event).where(Event.id == id).options(selectinload(Event.categories)) + select(Event) + .where(Event.id == id) + .options( + selectinload( + Event.gp_questions, + GPQuestion.categories, + ), + selectinload( + Event.categories, + ), + selectinload(Event.analysises, Analysis.category), + ) ) - # TODO: link to more models, give more data + if not event: + raise HTTPException(HTTPStatus.NOT_FOUND) + return event diff --git a/backend/src/events/schemas.py b/backend/src/events/schemas.py index ff3f93f6..c0fc3af9 100644 --- a/backend/src/events/schemas.py +++ b/backend/src/events/schemas.py @@ -3,19 +3,39 @@ from src.categories.schemas import CategoryDTO -class EventDTO(BaseModel): +class MiniEventDTO(BaseModel): model_config = ConfigDict(from_attributes=True) id: int title: str description: str - analysis: str is_singapore: bool date: datetime categories: list[CategoryDTO] +class AnalysisDTO(BaseModel): + model_config = ConfigDict(from_attributes=True) + id: int + category: CategoryDTO + content: str + + +class GPQuestionDTO(BaseModel): + model_config = ConfigDict(from_attributes=True) + id: int + question: str + is_llm_generated: bool + categories: list[CategoryDTO] + + +class EventDTO(MiniEventDTO): + model_config = ConfigDict(from_attributes=True) + analysises: list[AnalysisDTO] + gp_questions: list[GPQuestionDTO] + + class EventIndexResponse(BaseModel): total_count: int count: int - data: list[EventDTO] + data: list[MiniEventDTO] diff --git a/backend/src/scripts/seed.py b/backend/src/scripts/seed.py index 664c5320..387b6da2 100644 --- a/backend/src/scripts/seed.py +++ b/backend/src/scripts/seed.py @@ -1,6 +1,13 @@ from datetime import datetime from sqlalchemy import select -from src.events.models import Article, ArticleSource, Category, Event +from src.events.models import ( + Analysis, + Article, + ArticleSource, + Category, + Event, + GPQuestion, +) from sqlalchemy.orm import Session from src.common.database import engine @@ -40,6 +47,7 @@ def test_associations(): source=ArticleSource.CNA, body="test body", date="2024-02-05", + image_url="", ) event = Event( title="test event 1", @@ -47,9 +55,18 @@ def test_associations(): duplicate=False, date=datetime.now(), is_singapore=False, + rating=5, ) - article.events.append(event) + + analysis = Analysis(category_id=1, content="hello") + event.analysises.append(analysis) + event.gp_questions.append( + GPQuestion(question="whatever", is_llm_generated=False) + ) + + article.original_events.append(event) session.add(article) + session.add(event) session.commit() session.refresh(article) @@ -60,10 +77,10 @@ def test_associations(): with Session(engine) as session: event_again = session.scalar(select(Event).where(Event.id == event_id)) - categories = session.scalars( - select(Category).where(Category.name.in_(["Environment", "Media"])) - ) - event_again.categories.extend(categories) + # categories = session.scalars( + # select(Category).where(Category.name.in_(["Environment", "Media"])) + # ) + # event_again.categories.extend(categories) session.add(event_again) session.commit() @@ -73,11 +90,11 @@ def test_associations(): print(event_again.original_article) print(event_again.categories) event_again.categories.clear() - original_article = event_again.original_article + # original_article = event_again.original_article session.add(event_again) session.commit() - session.delete(event_again) - session.delete(original_article) + # session.delete(event_again) + # session.delete(original_article) -# test_associations() +test_associations()