diff --git a/api/src/db/migrations/versions/2025_01_08_create_saved_searches_table.py b/api/src/db/migrations/versions/2025_01_08_create_saved_searches_table.py new file mode 100644 index 000000000..487c78050 --- /dev/null +++ b/api/src/db/migrations/versions/2025_01_08_create_saved_searches_table.py @@ -0,0 +1,62 @@ +"""Create saved searches table + +Revision ID: cc2912373d5b +Revises: 232a9223ed9b +Create Date: 2025-01-08 16:20:40.898481 + +""" + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "cc2912373d5b" +down_revision = "232a9223ed9b" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "user_saved_search", + sa.Column("saved_search_id", sa.UUID(), nullable=False), + sa.Column("user_id", sa.UUID(), nullable=False), + sa.Column("search_query", postgresql.JSONB(astext_type=sa.Text()), nullable=False), + sa.Column("name", sa.Text(), nullable=False), + sa.Column( + "created_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + nullable=False, + ), + sa.Column( + "updated_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + nullable=False, + ), + sa.ForeignKeyConstraint( + ["user_id"], ["api.user.user_id"], name=op.f("user_saved_search_user_id_user_fkey") + ), + sa.PrimaryKeyConstraint("saved_search_id", name=op.f("user_saved_search_pkey")), + schema="api", + ) + op.create_index( + op.f("user_saved_search_user_id_idx"), + "user_saved_search", + ["user_id"], + unique=False, + schema="api", + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index( + op.f("user_saved_search_user_id_idx"), table_name="user_saved_search", schema="api" + ) + op.drop_table("user_saved_search", schema="api") + # ### end Alembic commands ### diff --git a/api/src/db/models/user_models.py b/api/src/db/models/user_models.py index c2ed8158b..50f6552b8 100644 --- a/api/src/db/models/user_models.py +++ b/api/src/db/models/user_models.py @@ -2,7 +2,7 @@ from datetime import datetime from sqlalchemy import BigInteger, ForeignKey -from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.dialects.postgresql import JSONB, UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from src.adapters.db.type_decorators.postgres_type_decorators import LookupColumn @@ -24,6 +24,10 @@ class User(ApiSchemaTable, TimestampMixin): cascade="all, delete-orphan", ) + saved_searches: Mapped[list["UserSavedSearch"]] = relationship( + "UserSavedSearch", back_populates="user", uselist=True, cascade="all, delete-orphan" + ) + class LinkExternalUser(ApiSchemaTable, TimestampMixin): __tablename__ = "link_external_user" @@ -82,3 +86,18 @@ class UserSavedOpportunity(ApiSchemaTable, TimestampMixin): opportunity: Mapped[Opportunity] = relationship( "Opportunity", back_populates="saved_opportunities_by_users" ) + + +class UserSavedSearch(ApiSchemaTable, TimestampMixin): + """Table for storing saved search queries for users""" + + __tablename__ = "user_saved_search" + + saved_search_id: Mapped[uuid.UUID] = mapped_column(UUID, primary_key=True, default=uuid.uuid4) + + user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey(User.user_id), index=True) + user: Mapped[User] = relationship(User, back_populates="saved_searches") + + search_query: Mapped[dict] = mapped_column(JSONB) + + name: Mapped[str] diff --git a/documentation/api/database/erds/api-schema.png b/documentation/api/database/erds/api-schema.png index 5f97fd5fa..f3a69945a 100644 Binary files a/documentation/api/database/erds/api-schema.png and b/documentation/api/database/erds/api-schema.png differ