Complete security audit integration into the enrichment pipeline:
Backend:
- SecurityAuditService with 7 passive checks: HTTPS, SSL cert, security
headers, exposed files, cookies, server info, technology detection
- Constants file with SECURITY_HEADERS, EXPOSED_PATHS, SEVERITY_SCORES
- SecurityAuditResponse schema with JSON field validators + aliases
- Endpoints: POST /security-audit/{id}, POST /security-audit/batch
- Added to full_enrichment pipeline (Step 5, before scoring)
- get_pending_security_audit() query in prospect_service
Frontend:
- Security tab on prospect detail page with grade badge (A+ to F),
score/100, severity counts, HTTPS/SSL status, missing headers,
exposed files, technologies, and full findings list
- "Run Security Audit" button with loading state
- "Security Audit" batch button on scan-jobs page
Tested on batirenovation-strasbourg.fr: Grade D (50/100), 11 issues
found (missing headers, exposed wp-login, server version disclosure).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
128 lines
3.7 KiB
Python
128 lines
3.7 KiB
Python
# app/modules/prospecting/schemas/prospect.py
|
|
"""Pydantic schemas for prospect management."""
|
|
|
|
from datetime import datetime
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class ProspectCreate(BaseModel):
|
|
"""Schema for creating a prospect."""
|
|
|
|
channel: str = Field("digital", pattern="^(digital|offline)$")
|
|
business_name: str | None = Field(None, max_length=255)
|
|
domain_name: str | None = Field(None, max_length=255)
|
|
source: str | None = Field(None, max_length=100)
|
|
address: str | None = Field(None, max_length=500)
|
|
city: str | None = Field(None, max_length=100)
|
|
postal_code: str | None = Field(None, max_length=10)
|
|
country: str = Field("LU", max_length=2)
|
|
notes: str | None = None
|
|
tags: list[str] | None = None
|
|
location_lat: float | None = None
|
|
location_lng: float | None = None
|
|
contacts: list["ProspectContactCreate"] | None = None
|
|
|
|
|
|
class ProspectUpdate(BaseModel):
|
|
"""Schema for updating a prospect."""
|
|
|
|
business_name: str | None = Field(None, max_length=255)
|
|
domain_name: str | None = Field(None, max_length=255)
|
|
status: str | None = None
|
|
source: str | None = Field(None, max_length=100)
|
|
address: str | None = Field(None, max_length=500)
|
|
city: str | None = Field(None, max_length=100)
|
|
postal_code: str | None = Field(None, max_length=10)
|
|
notes: str | None = None
|
|
tags: list[str] | None = None
|
|
|
|
|
|
class ProspectResponse(BaseModel):
|
|
"""Schema for prospect response."""
|
|
|
|
id: int
|
|
channel: str
|
|
business_name: str | None = None
|
|
domain_name: str | None = None
|
|
status: str
|
|
source: str | None = None
|
|
has_website: bool | None = None
|
|
uses_https: bool | None = None
|
|
http_status_code: int | None = None
|
|
address: str | None = None
|
|
city: str | None = None
|
|
postal_code: str | None = None
|
|
country: str = "LU"
|
|
notes: str | None = None
|
|
tags: list[str] | None = None
|
|
location_lat: float | None = None
|
|
location_lng: float | None = None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
# Nested (optional, included in detail view)
|
|
score: "ProspectScoreResponse | None" = None
|
|
primary_email: str | None = None
|
|
primary_phone: str | None = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class ProspectDetailResponse(ProspectResponse):
|
|
"""Full prospect detail with all related data."""
|
|
|
|
tech_profile: "TechProfileResponse | None" = None
|
|
performance_profile: "PerformanceProfileResponse | None" = None
|
|
security_audit: "SecurityAuditResponse | None" = None
|
|
contacts: list["ProspectContactResponse"] = []
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class ProspectListResponse(BaseModel):
|
|
"""Paginated prospect list response."""
|
|
|
|
items: list[ProspectResponse]
|
|
total: int
|
|
page: int
|
|
per_page: int
|
|
pages: int
|
|
|
|
|
|
class ProspectDeleteResponse(BaseModel):
|
|
"""Response for prospect deletion."""
|
|
|
|
message: str
|
|
|
|
|
|
class ProspectImportResponse(BaseModel):
|
|
"""Response for domain import."""
|
|
|
|
created: int
|
|
skipped: int
|
|
total: int
|
|
|
|
|
|
# Forward references resolved at module level
|
|
from app.modules.prospecting.schemas.contact import ( # noqa: E402
|
|
ProspectContactCreate,
|
|
ProspectContactResponse,
|
|
)
|
|
from app.modules.prospecting.schemas.performance_profile import (
|
|
PerformanceProfileResponse, # noqa: E402
|
|
)
|
|
from app.modules.prospecting.schemas.score import ProspectScoreResponse # noqa: E402
|
|
from app.modules.prospecting.schemas.security_audit import (
|
|
SecurityAuditResponse, # noqa: E402
|
|
)
|
|
from app.modules.prospecting.schemas.tech_profile import (
|
|
TechProfileResponse, # noqa: E402
|
|
)
|
|
|
|
ProspectCreate.model_rebuild()
|
|
ProspectResponse.model_rebuild()
|
|
ProspectDetailResponse.model_rebuild()
|