feat: Sprint 1 - Deep discovery, lead persistence, and dashboard nav

- Add BacklinkOutreachScraper (Exa + DuckDuckGo deep scraping)
- Extend DB and Pydantic models for lead enrichment columns
- Add StorageService methods for lead CRUD with auto-migration
- Add backend endpoints: deep discover, campaign detail, lead management
- Extend frontend API client and store with discovery + lead actions
- Create BacklinkOutreachDashboard component with campaigns/discover/leads tabs
- Register route at /backlink-outreach under SEO feature flag
- Add nav entry under Enterprise & Advanced in tool categories
This commit is contained in:
ajaysi
2026-05-23 17:07:33 +05:30
parent 816d59a30a
commit 090d69761f
22 changed files with 3494 additions and 48 deletions

View File

@@ -29,6 +29,83 @@ class BacklinkDiscoveryResponse(BaseModel):
opportunities: List[OpportunityRecord]
# -- Deep Discovery Models --
class DeepKeywordInput(BaseModel):
keyword: str = Field(..., min_length=2, max_length=120)
max_results: int = Field(default=15, ge=1, le=50)
campaign_id: Optional[str] = Field(default=None, description="If set, auto-saves leads to this campaign")
class EnrichedOpportunity(BaseModel):
url: str
domain: str
page_title: str = ""
snippet: str = ""
full_text: str = ""
email: Optional[str] = None
contact_page: Optional[str] = None
confidence_score: float = Field(default=0.0, ge=0.0, le=1.0)
quality_score: float = Field(default=0.0, ge=0.0, le=1.0)
word_count: int = 0
has_guest_post_guidelines: bool = False
discovery_source: str = "duckduckgo"
class DeepDiscoveryResponse(BaseModel):
keyword: str
source: str
total_found: int
opportunities: List[EnrichedOpportunity]
# -- Lead Models --
class LeadCreateRequest(BaseModel):
campaign_id: str = Field(..., min_length=1)
url: str = Field(..., min_length=1)
domain: str = Field(..., min_length=1)
email: Optional[str] = None
page_title: Optional[str] = None
snippet: Optional[str] = None
confidence_score: float = Field(default=0.0, ge=0.0, le=1.0)
notes: Optional[str] = None
class LeadRecord(BaseModel):
lead_id: str
campaign_id: str
url: Optional[str]
domain: str
page_title: Optional[str] = ""
snippet: Optional[str] = ""
email: Optional[str] = None
confidence_score: float = 0.0
discovery_source: Optional[str] = "duckduckgo"
status: str = "discovered"
notes: Optional[str] = None
created_at: Optional[str] = None
class LeadListResponse(BaseModel):
leads: List[LeadRecord]
total: int
class LeadStatusUpdateRequest(BaseModel):
status: str = Field(..., min_length=1)
notes: Optional[str] = None
class CampaignDetailResponse(BaseModel):
campaign_id: str
name: str
status: str
created_at: Optional[str] = None
lead_count: int = 0
leads: List[LeadRecord] = Field(default_factory=list)
class GeneratedEmailResponse(BaseModel):
subject: str
body: str