feat(prospecting): add ProspectSecurityAudit model (Phase 1 foundation)
- New model: ProspectSecurityAudit with score, grade, findings_json, severity counts, has_https, has_valid_ssl, missing_headers, exposed files, technologies, scan_error - Add last_security_audit_at timestamp to Prospect model - Add security_audit 1:1 relationship on Prospect Part of Phase 1: Security Audit in Enrichment Pipeline. Service, constants, migration, endpoints, and frontend to follow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ from app.modules.prospecting.models.prospect import (
|
|||||||
from app.modules.prospecting.models.prospect_contact import ContactType, ProspectContact
|
from app.modules.prospecting.models.prospect_contact import ContactType, ProspectContact
|
||||||
from app.modules.prospecting.models.prospect_score import ProspectScore
|
from app.modules.prospecting.models.prospect_score import ProspectScore
|
||||||
from app.modules.prospecting.models.scan_job import JobStatus, JobType, ProspectScanJob
|
from app.modules.prospecting.models.scan_job import JobStatus, JobType, ProspectScanJob
|
||||||
|
from app.modules.prospecting.models.security_audit import ProspectSecurityAudit
|
||||||
from app.modules.prospecting.models.tech_profile import ProspectTechProfile
|
from app.modules.prospecting.models.tech_profile import ProspectTechProfile
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@@ -44,4 +45,5 @@ __all__ = [
|
|||||||
"CampaignChannel",
|
"CampaignChannel",
|
||||||
"CampaignSendStatus",
|
"CampaignSendStatus",
|
||||||
"LeadType",
|
"LeadType",
|
||||||
|
"ProspectSecurityAudit",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -69,10 +69,12 @@ class Prospect(Base, TimestampMixin):
|
|||||||
last_tech_scan_at = Column(DateTime, nullable=True)
|
last_tech_scan_at = Column(DateTime, nullable=True)
|
||||||
last_perf_scan_at = Column(DateTime, nullable=True)
|
last_perf_scan_at = Column(DateTime, nullable=True)
|
||||||
last_contact_scrape_at = Column(DateTime, nullable=True)
|
last_contact_scrape_at = Column(DateTime, nullable=True)
|
||||||
|
last_security_audit_at = Column(DateTime, nullable=True)
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
tech_profile = relationship("ProspectTechProfile", back_populates="prospect", uselist=False, cascade="all, delete-orphan")
|
tech_profile = relationship("ProspectTechProfile", back_populates="prospect", uselist=False, cascade="all, delete-orphan")
|
||||||
performance_profile = relationship("ProspectPerformanceProfile", back_populates="prospect", uselist=False, cascade="all, delete-orphan")
|
performance_profile = relationship("ProspectPerformanceProfile", back_populates="prospect", uselist=False, cascade="all, delete-orphan")
|
||||||
|
security_audit = relationship("ProspectSecurityAudit", back_populates="prospect", uselist=False, cascade="all, delete-orphan")
|
||||||
score = relationship("ProspectScore", back_populates="prospect", uselist=False, cascade="all, delete-orphan")
|
score = relationship("ProspectScore", back_populates="prospect", uselist=False, cascade="all, delete-orphan")
|
||||||
contacts = relationship("ProspectContact", back_populates="prospect", cascade="all, delete-orphan")
|
contacts = relationship("ProspectContact", back_populates="prospect", cascade="all, delete-orphan")
|
||||||
interactions = relationship("ProspectInteraction", back_populates="prospect", cascade="all, delete-orphan")
|
interactions = relationship("ProspectInteraction", back_populates="prospect", cascade="all, delete-orphan")
|
||||||
|
|||||||
59
app/modules/prospecting/models/security_audit.py
Normal file
59
app/modules/prospecting/models/security_audit.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# app/modules/prospecting/models/security_audit.py
|
||||||
|
"""
|
||||||
|
Security audit results for a prospect's website.
|
||||||
|
|
||||||
|
Stores findings from passive security checks (HTTPS, headers, exposed files,
|
||||||
|
cookies, server info, technology detection). Follows the same 1:1 pattern as
|
||||||
|
ProspectTechProfile and ProspectPerformanceProfile.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Text
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
from app.core.database import Base
|
||||||
|
from models.database.base import TimestampMixin
|
||||||
|
|
||||||
|
|
||||||
|
class ProspectSecurityAudit(Base, TimestampMixin):
|
||||||
|
"""Security audit results for a prospect's website."""
|
||||||
|
|
||||||
|
__tablename__ = "prospect_security_audits"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
prospect_id = Column(
|
||||||
|
Integer,
|
||||||
|
ForeignKey("prospects.id", ondelete="CASCADE"),
|
||||||
|
nullable=False,
|
||||||
|
unique=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Overall score and grade
|
||||||
|
score = Column(Integer, nullable=False, default=0) # 0-100
|
||||||
|
grade = Column(String(2), nullable=False, default="F") # A+, A, B, C, D, F
|
||||||
|
|
||||||
|
# Detected language for bilingual reports
|
||||||
|
detected_language = Column(String(5), nullable=True, default="en")
|
||||||
|
|
||||||
|
# Findings stored as JSON (variable structure per check)
|
||||||
|
findings_json = Column(Text, nullable=True) # JSON list of finding dicts
|
||||||
|
|
||||||
|
# Denormalized severity counts (for dashboard queries without JSON parsing)
|
||||||
|
findings_count_critical = Column(Integer, nullable=False, default=0)
|
||||||
|
findings_count_high = Column(Integer, nullable=False, default=0)
|
||||||
|
findings_count_medium = Column(Integer, nullable=False, default=0)
|
||||||
|
findings_count_low = Column(Integer, nullable=False, default=0)
|
||||||
|
findings_count_info = Column(Integer, nullable=False, default=0)
|
||||||
|
|
||||||
|
# Key results (denormalized for quick access)
|
||||||
|
has_https = Column(Boolean, nullable=True)
|
||||||
|
has_valid_ssl = Column(Boolean, nullable=True)
|
||||||
|
ssl_expires_at = Column(DateTime, nullable=True)
|
||||||
|
missing_headers_json = Column(Text, nullable=True) # JSON list of header names
|
||||||
|
exposed_files_json = Column(Text, nullable=True) # JSON list of exposed paths
|
||||||
|
technologies_json = Column(Text, nullable=True) # JSON list of detected techs
|
||||||
|
|
||||||
|
# Scan metadata
|
||||||
|
scan_error = Column(Text, nullable=True)
|
||||||
|
|
||||||
|
# Relationships
|
||||||
|
prospect = relationship("Prospect", back_populates="security_audit")
|
||||||
Reference in New Issue
Block a user