diff --git a/backend-api/alembic/versions/a9befc7a716c_add_manual_verification_table.py b/backend-api/alembic/versions/a9befc7a716c_add_manual_verification_table.py new file mode 100644 index 000000000..b0eee4a63 --- /dev/null +++ b/backend-api/alembic/versions/a9befc7a716c_add_manual_verification_table.py @@ -0,0 +1,48 @@ +"""Add manual_verification table + +Revision ID: a9befc7a716c +Revises: j2k3l4m5n678 +Create Date: 2026-04-04 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = "a9befc7a716c" +down_revision: Union[str, Sequence[str], None] = "j1k2l3m4n567" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + op.create_table( + "manual_verification", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("scan_id", sa.Integer(), nullable=False), + sa.Column("control_id", sa.String(length=50), nullable=False), + sa.Column("user_id", sa.Integer(), nullable=False), + sa.Column("status", sa.String(length=20), nullable=False), + sa.Column("comment", sa.Text(), nullable=True), + sa.Column( + "created_at", sa.DateTime(), server_default=sa.text("now()"), nullable=False + ), + sa.Column( + "updated_at", + sa.DateTime(), + server_default=sa.text("now()"), + nullable=False, + ), + sa.ForeignKeyConstraint(["scan_id"], ["scan.id"], ondelete="CASCADE"), + sa.ForeignKeyConstraint(["user_id"], ["user.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + + +def downgrade() -> None: + """Downgrade schema.""" + op.drop_table("manual_verification") \ No newline at end of file diff --git a/backend-api/app/models/__init__.py b/backend-api/app/models/__init__.py index 27c476e4d..6b3b4e71f 100644 --- a/backend-api/app/models/__init__.py +++ b/backend-api/app/models/__init__.py @@ -8,6 +8,7 @@ from app.models.aws_connection import AWSConnection from app.models.platform import Platform from app.models.scan_result import ScanResult +from app.models.manual_verification import ManualVerification from app.models.compliance import Scan from app.models.evidence_validation import EvidenceValidation from app.models.contact import ContactSubmission, SubmissionNote, SubmissionHistory @@ -23,6 +24,7 @@ "AWSConnection", "Platform", "ScanResult", + "ManualVerification", "Scan", "EvidenceValidation", "ContactSubmission", diff --git a/backend-api/app/models/manual_verification.py b/backend-api/app/models/manual_verification.py new file mode 100644 index 000000000..b7cd86767 --- /dev/null +++ b/backend-api/app/models/manual_verification.py @@ -0,0 +1,34 @@ +"""manualVerification Model for user-submitted control verifications.""" + +from datetime import datetime +from typing import TYPE_CHECKING, Optional + +from sqlalchemy import ForeignKey, String, Text +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.sql import func + +from app.db.base import Base + +if TYPE_CHECKING: + from app.models.compliance import Scan # prevent version conflict with circular import + from app.models.user import User # prevent version conflict with circular import + +class ManualVerification(Base): + """Model for manual verification of controls by users.""" + + __tablename__ = "manual_verification" + + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + scan_id: Mapped[int] = mapped_column(ForeignKey("scan.id"), nullable=False) + control_id: Mapped[str] = mapped_column(String(50), nullable=False) + user_id: Mapped[int] = mapped_column(ForeignKey("user.id"), nullable=False) + # Status: passed or failed + status: Mapped[str] = mapped_column(String(20), nullable=False) + comment: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + + created_at: Mapped[datetime] = mapped_column(server_default=func.now(), nullable=False) + updated_at: Mapped[datetime] = mapped_column(server_default=func.now(), onupdate=func.now(), nullable=False) + + # Relationships + scan: Mapped["Scan"] = relationship() + user: Mapped["User"] = relationship() \ No newline at end of file