diff --git a/alembic/versions/20240617_1359_5f5835d81917_add_proof_date_field.py b/alembic/versions/20240617_1359_5f5835d81917_add_proof_date_field.py new file mode 100644 index 00000000..3fe7a0bb --- /dev/null +++ b/alembic/versions/20240617_1359_5f5835d81917_add_proof_date_field.py @@ -0,0 +1,43 @@ +"""Add Proof date field + +Revision ID: 5f5835d81917 +Revises: 818f9ced20cd +Create Date: 2024-06-17 13:59:42.294690 + +""" +from typing import Sequence, Union + +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "5f5835d81917" +down_revision: Union[str, None] = "818f9ced20cd" +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("proofs", sa.Column("date", sa.Date(), nullable=True)) + # Set the date to the date of the first price for each proof + op.execute( + """ + UPDATE proofs + SET date = ( + SELECT date + FROM prices + WHERE prices.proof_id = proofs.id + LIMIT 1 + ) + WHERE type IN ('PRICE_TAG', 'RECEIPT') + """ + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("proofs", "date") + # ### end Alembic commands ### diff --git a/app/crud.py b/app/crud.py index 9a29bf3a..afadb490 100644 --- a/app/crud.py +++ b/app/crud.py @@ -383,6 +383,7 @@ def create_proof( mimetype: str, type: ProofTypeEnum, user: UserCreate, + date: str = None, source: str = None, ) -> Proof: """Create a proof in the database. @@ -397,6 +398,7 @@ def create_proof( file_path=file_path, mimetype=mimetype, type=type, + date=date, owner=user.user_id, source=source, ) diff --git a/app/models.py b/app/models.py index 3c549514..a3919724 100644 --- a/app/models.py +++ b/app/models.py @@ -124,6 +124,8 @@ class Proof(Base): Integer, nullable=False, server_default="0", index=True ) + date = mapped_column(Date) + owner = mapped_column(String, index=True) source = mapped_column(String, nullable=True) diff --git a/app/routers/proofs.py b/app/routers/proofs.py index 0f0fc42a..32cb3831 100644 --- a/app/routers/proofs.py +++ b/app/routers/proofs.py @@ -40,6 +40,7 @@ def upload_proof( file: UploadFile, type: Annotated[ProofTypeEnum, Form(description="The type of the proof")], current_user: schemas.UserCreate = Depends(get_current_user), + date: str | None = None, app_name: str | None = None, db: Session = Depends(get_db), ) -> Proof: @@ -53,7 +54,13 @@ def upload_proof( """ file_path, mimetype = crud.create_proof_file(file) db_proof = crud.create_proof( - db, file_path, mimetype, type=type, user=current_user, source=app_name + db, + file_path, + mimetype, + type=type, + user=current_user, + date=date, + source=app_name, ) return db_proof diff --git a/app/schemas.py b/app/schemas.py index 24fed623..d029b354 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -185,6 +185,9 @@ class ProofFull(BaseModel): price_count: int = Field( description="number of prices for this proof.", examples=[15], default=0 ) + date: datetime.date | None = Field( + description="date of the proof.", examples=["2024-01-01"] + ) owner: str # source: str | None = Field( # description="Source (App name)", @@ -199,6 +202,7 @@ class ProofFull(BaseModel): class ProofBasicUpdatableFields(BaseModel): type: ProofTypeEnum | None = None + date: datetime.date | None = None class Config: extra = "forbid" diff --git a/tests/integration/test_api.py b/tests/integration/test_api.py index 499dc4b7..a99bcf10 100644 --- a/tests/integration/test_api.py +++ b/tests/integration/test_api.py @@ -845,6 +845,13 @@ def test_create_proof(db_session, user_session: SessionModel, clean_proofs): headers={"Authorization": f"Bearer {user_session.token}"}, ) assert response.status_code == 422 + # with authentication but validation error (date format) + response = client.post( + "/api/v1/proofs/upload", + data={"type": "PRICE_TAG", "date": "test"}, + headers={"Authorization": f"Bearer {user_session.token}"}, + ) + assert response.status_code == 422 # with authentication and no validation error response = client.post( @@ -859,7 +866,7 @@ def test_create_proof(db_session, user_session: SessionModel, clean_proofs): response = client.post( "/api/v1/proofs/upload", files={"file": ("filename", (io.BytesIO(b"test")), "image/webp")}, - data={"type": "RECEIPT"}, + data={"type": "RECEIPT", "date": "2024-01-01"}, headers={"Authorization": f"Bearer {user_session.token}"}, ) assert response.status_code == 201 @@ -905,6 +912,7 @@ def test_get_proofs(db_session, user_session: SessionModel, clean_proofs): "mimetype", "type", "price_count", + "date", "owner", "created", "updated", @@ -1034,6 +1042,16 @@ def test_update_proof( ) assert response.status_code == 200 assert response.json()["type"] == "RECEIPT" + assert response.json()["date"] is None + PROOF_UPDATE_PARTIAL = {"date": "2024-01-01"} + response = client.patch( + f"/api/v1/proofs/{proof.id}", + headers={"Authorization": f"Bearer {user_session.token}"}, + json=jsonable_encoder(PROOF_UPDATE_PARTIAL), + ) + assert response.status_code == 200 + assert response.json()["type"] == "RECEIPT" + assert response.json()["date"] == "2024-01-01" # with authentication and proof owner but extra fields PROOF_UPDATE_PARTIAL_WRONG = {**PROOF_UPDATE_PARTIAL, "owner": 1}