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_score import ProspectScore
|
||||
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
|
||||
|
||||
__all__ = [
|
||||
@@ -44,4 +45,5 @@ __all__ = [
|
||||
"CampaignChannel",
|
||||
"CampaignSendStatus",
|
||||
"LeadType",
|
||||
"ProspectSecurityAudit",
|
||||
]
|
||||
|
||||
@@ -69,10 +69,12 @@ class Prospect(Base, TimestampMixin):
|
||||
last_tech_scan_at = Column(DateTime, nullable=True)
|
||||
last_perf_scan_at = Column(DateTime, nullable=True)
|
||||
last_contact_scrape_at = Column(DateTime, nullable=True)
|
||||
last_security_audit_at = Column(DateTime, nullable=True)
|
||||
|
||||
# Relationships
|
||||
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")
|
||||
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")
|
||||
contacts = relationship("ProspectContact", 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