feat(campaigns): campaign start date — scheduled activation, Jira start_date
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
DB: migration b047 adds start_date (DateTime nullable) + index to campaigns. Backend: - Campaign model: start_date field - CampaignCreate/Update schemas: accept start_date (ISO string) - CRUD service: persist + serialize start_date in both serializers - Activation endpoint: blocks manual activation if start_date is in the future (campaign will auto-activate via scheduler) - Scheduler: new hourly job _run_scheduled_campaign_activation — finds draft campaigns with start_date <= now, activates them, creates Jira tickets, notifies red_tech team - Jira: campaign + test tickets now include JIRA_START_DATE_FIELD (configurable, default customfield_10015). Campaign uses start_date if set, else created_at. Tests inherit campaign start_date. - config.py: JIRA_START_DATE_FIELD setting Frontend: - Campaign type: start_date field on Campaign + CampaignSummary - CampaignCreatePayload: start_date optional field - Create form: date picker with min=today, warning message explaining behavior - Campaign detail header: start_date badge showing days remaining or started date Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -72,6 +72,7 @@ def serialize_campaign(db: Session, campaign: Campaign) -> dict:
|
||||
"threat_actor_id": str(campaign.threat_actor_id) if campaign.threat_actor_id else None,
|
||||
"threat_actor_name": actor.name if actor else None,
|
||||
"created_by": str(campaign.created_by) if campaign.created_by else None,
|
||||
"start_date": campaign.start_date.isoformat() if campaign.start_date else None,
|
||||
"scheduled_at": campaign.scheduled_at.isoformat() if campaign.scheduled_at else None,
|
||||
"completed_at": campaign.completed_at.isoformat() if campaign.completed_at else None,
|
||||
"target_platform": campaign.target_platform,
|
||||
@@ -100,6 +101,7 @@ def serialize_campaign_summary(db: Session, campaign: Campaign) -> dict:
|
||||
"status": campaign.status,
|
||||
"threat_actor_id": str(campaign.threat_actor_id) if campaign.threat_actor_id else None,
|
||||
"threat_actor_name": actor.name if actor else None,
|
||||
"start_date": campaign.start_date.isoformat() if campaign.start_date else None,
|
||||
"target_platform": campaign.target_platform,
|
||||
"tags": campaign.tags or [],
|
||||
"created_at": campaign.created_at.isoformat() if campaign.created_at else None,
|
||||
@@ -160,6 +162,7 @@ def create_campaign(
|
||||
target_platform: Optional[str] = None,
|
||||
tags: Optional[list[str]] = None,
|
||||
scheduled_at: Optional[str] = None,
|
||||
start_date: Optional[str] = None,
|
||||
) -> dict:
|
||||
"""Create a new campaign. Does not commit; caller commits."""
|
||||
campaign = Campaign(
|
||||
@@ -171,6 +174,7 @@ def create_campaign(
|
||||
tags=tags or [],
|
||||
created_by=creator_id,
|
||||
scheduled_at=datetime.fromisoformat(scheduled_at) if scheduled_at else None,
|
||||
start_date=datetime.fromisoformat(start_date) if start_date else None,
|
||||
)
|
||||
db.add(campaign)
|
||||
db.flush()
|
||||
@@ -213,6 +217,8 @@ def update_campaign(
|
||||
|
||||
if "scheduled_at" in fields and fields["scheduled_at"]:
|
||||
fields["scheduled_at"] = datetime.fromisoformat(fields["scheduled_at"])
|
||||
if "start_date" in fields and fields["start_date"]:
|
||||
fields["start_date"] = datetime.fromisoformat(fields["start_date"])
|
||||
|
||||
for field, value in fields.items():
|
||||
setattr(campaign, field, value)
|
||||
|
||||
Reference in New Issue
Block a user