diff --git a/alembic/versions/2025_03_11_1539-69f7cc4f56d4_create_approving_user_url_table.py b/alembic/versions/2025_03_11_1539-69f7cc4f56d4_create_approving_user_url_table.py new file mode 100644 index 00000000..f38d33dc --- /dev/null +++ b/alembic/versions/2025_03_11_1539-69f7cc4f56d4_create_approving_user_url_table.py @@ -0,0 +1,34 @@ +"""Create approving_user_url table + +Revision ID: 69f7cc4f56d4 +Revises: 33421c0590bb +Create Date: 2025-03-11 15:39:27.563567 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '69f7cc4f56d4' +down_revision: Union[str, None] = '33421c0590bb' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.create_table( + 'approving_user_url', + sa.Column('id', sa.Integer(), nullable=False, primary_key=True), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('url_id', sa.Integer(), nullable=False), + sa.Column('created_at', sa.TIMESTAMP(), nullable=False, server_default=sa.text('now()')), + sa.ForeignKeyConstraint(['url_id'], ['urls.id'], ), + sa.UniqueConstraint('url_id', name='approving_user_url_uq_user_id_url_id') + ) + + +def downgrade() -> None: + op.drop_table('approving_user_url') diff --git a/api/routes/review.py b/api/routes/review.py index 0933d27d..61dccbbb 100644 --- a/api/routes/review.py +++ b/api/routes/review.py @@ -27,5 +27,8 @@ async def approve_source( access_info: AccessInfo = Depends(get_access_info), approval_info: FinalReviewApprovalInfo = FinalReviewApprovalInfo ) -> GetNextURLForFinalReviewOuterResponse: - next_source = await core.approve_and_get_next_source_for_review(approval_info) + next_source = await core.approve_and_get_next_source_for_review( + approval_info, + access_info=access_info + ) return GetNextURLForFinalReviewOuterResponse(next_source=next_source) \ No newline at end of file diff --git a/collector_db/AsyncDatabaseClient.py b/collector_db/AsyncDatabaseClient.py index 81d6dd02..82fedc93 100644 --- a/collector_db/AsyncDatabaseClient.py +++ b/collector_db/AsyncDatabaseClient.py @@ -22,7 +22,7 @@ from collector_db.models import URL, URLErrorInfo, URLHTMLContent, Base, \ RootURL, Task, TaskError, LinkTaskURL, Batch, Agency, AutomatedUrlAgencySuggestion, \ UserUrlAgencySuggestion, AutoRelevantSuggestion, AutoRecordTypeSuggestion, UserRelevantSuggestion, \ - UserRecordTypeSuggestion + UserRecordTypeSuggestion, ApprovingUserURL from collector_manager.enums import URLStatus, CollectorType from core.DTOs.GetNextRecordTypeAnnotationResponseInfo import GetNextRecordTypeAnnotationResponseInfo from core.DTOs.GetNextRelevanceAnnotationResponseInfo import GetNextRelevanceAnnotationResponseInfo @@ -1107,7 +1107,8 @@ async def approve_url( url_id: int, record_type: RecordType, relevant: bool, - agency_id: Optional[int] = None + user_id: int, + agency_id: Optional[int] = None, ) -> None: # Get URL @@ -1135,3 +1136,10 @@ async def approve_url( # If it does, do nothing url.outcome = URLStatus.VALIDATED.value + + approving_user_url = ApprovingUserURL( + user_id=user_id, + url_id=url_id + ) + + session.add(approving_user_url) diff --git a/collector_db/models.py b/collector_db/models.py index 51fc4a2a..7990eb65 100644 --- a/collector_db/models.py +++ b/collector_db/models.py @@ -128,6 +128,24 @@ class URL(Base): "AutoRelevantSuggestion", uselist=False, back_populates="url") user_relevant_suggestions = relationship( "UserRelevantSuggestion", back_populates="url") + approving_users = relationship( + "ApprovingUserURL", back_populates="url") + +class ApprovingUserURL(Base): + __tablename__ = 'approving_user_url' + __table_args__ = ( + UniqueConstraint( + "url_id", + name="approving_user_url_uq_user_id_url_id"), + ) + + id = Column(Integer, primary_key=True) + user_id = Column(Integer, nullable=False) + url_id = Column(Integer, ForeignKey('urls.id'), nullable=False) + created_at = get_created_at_column() + + # Relationships + url = relationship("URL", back_populates="approving_users") class RootURL(Base): __tablename__ = 'root_url_cache' diff --git a/core/AsyncCore.py b/core/AsyncCore.py index a576082f..4854926e 100644 --- a/core/AsyncCore.py +++ b/core/AsyncCore.py @@ -30,6 +30,7 @@ from llm_api_logic.OpenAIRecordClassifier import OpenAIRecordClassifier from pdap_api_client.AccessManager import AccessManager from pdap_api_client.PDAPClient import PDAPClient +from security_manager.SecurityManager import AccessInfo from util.helper_functions import get_from_env @@ -213,12 +214,14 @@ async def get_next_source_for_review(self): async def approve_and_get_next_source_for_review( self, - approval_info: FinalReviewApprovalInfo + approval_info: FinalReviewApprovalInfo, + access_info: AccessInfo ): await self.adb_client.approve_url( url_id=approval_info.url_id, record_type=approval_info.record_type, relevant=approval_info.relevant, - agency_id=approval_info.agency_id + agency_id=approval_info.agency_id, + user_id=access_info.user_id ) return await self.get_next_source_for_review() diff --git a/tests/test_automated/integration/collector_db/test_db_client.py b/tests/test_automated/integration/collector_db/test_db_client.py index 92a44dca..b8ac56f1 100644 --- a/tests/test_automated/integration/collector_db/test_db_client.py +++ b/tests/test_automated/integration/collector_db/test_db_client.py @@ -9,7 +9,7 @@ from collector_db.DTOs.URLInfo import URLInfo from collector_db.DTOs.URLMetadataInfo import URLMetadataInfo from collector_db.enums import URLMetadataAttributeType, ValidationStatus, ValidationSource -from collector_db.models import URL +from collector_db.models import URL, ApprovingUserURL from collector_manager.enums import URLStatus from core.enums import BatchStatus, RecordType, SuggestionType from tests.helpers.DBDataCreator import DBDataCreator @@ -331,7 +331,8 @@ async def test_approve_url_basic(db_data_creator: DBDataCreator): await adb_client.approve_url( url_mapping.url_id, record_type=RecordType.ARREST_RECORDS, - relevant=True + relevant=True, + user_id=1 ) # Confirm same agency id is listed as confirmed @@ -344,3 +345,8 @@ async def test_approve_url_basic(db_data_creator: DBDataCreator): assert url.relevant == True assert url.outcome == URLStatus.VALIDATED.value + approving_user_urls = await adb_client.get_all(ApprovingUserURL) + assert len(approving_user_urls) == 1 + assert approving_user_urls[0].user_id == 1 + assert approving_user_urls[0].url_id == url_mapping.url_id +