Initial commit: CI Agent competitive-intelligence monitoring app
FastAPI + Celery + Next.js + Postgres/Redis app with company monitoring, source collection, LLM-based change analysis, enrichment, and account security (Turnstile, escalating lockout, email verification).
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Self
|
||||
|
||||
from email_validator import EmailNotValidError, validate_email
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
from app.models.enums import NotificationType, SeverityLevel
|
||||
from app.models.notification_destination import NotificationDestination
|
||||
|
||||
_PHONE_RE = re.compile(r"^\+?[1-9]\d{7,14}$")
|
||||
|
||||
|
||||
class NotificationDestinationCreate(BaseModel):
|
||||
type: NotificationType
|
||||
destination_value: str = Field(min_length=1, max_length=320)
|
||||
minimum_severity: SeverityLevel = SeverityLevel.MEDIUM
|
||||
enabled: bool = True
|
||||
company_ids: list[uuid.UUID] = Field(
|
||||
min_length=1,
|
||||
description="Which companies this destination receives alerts for. If a destination "
|
||||
"with the same type/value already exists for this user, it's reused (linked to these "
|
||||
"companies too) rather than duplicated.",
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _validate_destination_value(self) -> Self:
|
||||
if self.type == NotificationType.EMAIL:
|
||||
try:
|
||||
validate_email(self.destination_value, check_deliverability=False)
|
||||
except EmailNotValidError as exc:
|
||||
raise ValueError(f"Invalid email address: {exc}") from exc
|
||||
elif self.type == NotificationType.SMS:
|
||||
if not _PHONE_RE.match(self.destination_value):
|
||||
raise ValueError("Phone number must be in E.164 format, e.g. +15551234567")
|
||||
return self
|
||||
|
||||
|
||||
class NotificationDestinationUpdate(BaseModel):
|
||||
destination_value: str | None = Field(default=None, min_length=1, max_length=320)
|
||||
enabled: bool | None = None
|
||||
minimum_severity: SeverityLevel | None = None
|
||||
|
||||
|
||||
class LinkedCompany(BaseModel):
|
||||
id: uuid.UUID
|
||||
name: str
|
||||
|
||||
|
||||
class NotificationDestinationResponse(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: uuid.UUID
|
||||
type: NotificationType
|
||||
destination_value: str
|
||||
verified: bool
|
||||
enabled: bool
|
||||
minimum_severity: SeverityLevel
|
||||
created_at: datetime
|
||||
companies: list[LinkedCompany] = Field(default_factory=list)
|
||||
|
||||
@classmethod
|
||||
def from_destination(cls, destination: NotificationDestination) -> Self:
|
||||
return cls(
|
||||
id=destination.id,
|
||||
type=destination.type,
|
||||
destination_value=destination.destination_value,
|
||||
verified=destination.verified,
|
||||
enabled=destination.enabled,
|
||||
minimum_severity=destination.minimum_severity,
|
||||
created_at=destination.created_at,
|
||||
companies=[
|
||||
LinkedCompany(id=link.company.id, name=link.company.name)
|
||||
for link in destination.company_links
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
class NotificationTestResult(BaseModel):
|
||||
success: bool
|
||||
error: str | None = None
|
||||
Reference in New Issue
Block a user