feat: global notification policy severity to channels (notif-22)
Singleton notification_policy table, NOTIFY_MIN_SEVERITY/CHANNELS env, central dispatch gate, Settings policy UI, migration 007. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5,6 +5,11 @@ from sqlalchemy.orm import Session
|
||||
from app.auth.jwt_auth import get_current_user
|
||||
from app.database import get_db
|
||||
from app.services.email_notify import EmailNotConfiguredError, EmailSendError, send_email_test_message
|
||||
from app.services.notification_policy import (
|
||||
NotificationPolicyConfig,
|
||||
get_effective_notification_policy,
|
||||
upsert_notification_policy,
|
||||
)
|
||||
from app.services.notification_settings import (
|
||||
VALID_SEVERITIES,
|
||||
EmailConfig,
|
||||
@@ -75,22 +80,36 @@ class EmailSettingsResponse(BaseModel):
|
||||
source: str = Field(description="env или db")
|
||||
|
||||
|
||||
class NotificationPolicyResponse(BaseModel):
|
||||
min_severity: str
|
||||
use_telegram: bool
|
||||
use_webhook: bool
|
||||
use_email: bool
|
||||
source: str = Field(description="env или db")
|
||||
|
||||
|
||||
class NotificationSettingsResponse(BaseModel):
|
||||
policy: NotificationPolicyResponse
|
||||
telegram: TelegramSettingsResponse
|
||||
webhook: WebhookSettingsResponse
|
||||
email: EmailSettingsResponse
|
||||
|
||||
|
||||
class NotificationPolicyUpdate(BaseModel):
|
||||
min_severity: str
|
||||
use_telegram: bool
|
||||
use_webhook: bool
|
||||
use_email: bool
|
||||
|
||||
|
||||
class TelegramSettingsUpdate(BaseModel):
|
||||
enabled: bool
|
||||
min_severity: str
|
||||
bot_token: str | None = None
|
||||
chat_id: str | None = None
|
||||
|
||||
|
||||
class WebhookSettingsUpdate(BaseModel):
|
||||
enabled: bool
|
||||
min_severity: str
|
||||
url: str | None = None
|
||||
secret_header: str | None = None
|
||||
secret: str | None = None
|
||||
@@ -98,7 +117,6 @@ class WebhookSettingsUpdate(BaseModel):
|
||||
|
||||
class EmailSettingsUpdate(BaseModel):
|
||||
enabled: bool
|
||||
min_severity: str
|
||||
smtp_host: str | None = None
|
||||
smtp_port: int | None = None
|
||||
smtp_user: str | None = None
|
||||
@@ -114,30 +132,40 @@ class ChannelTestResponse(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
def _telegram_to_response(cfg: TelegramConfig) -> TelegramSettingsResponse:
|
||||
def _policy_to_response(cfg: NotificationPolicyConfig) -> NotificationPolicyResponse:
|
||||
return NotificationPolicyResponse(
|
||||
min_severity=cfg.min_severity,
|
||||
use_telegram=cfg.use_telegram,
|
||||
use_webhook=cfg.use_webhook,
|
||||
use_email=cfg.use_email,
|
||||
source=cfg.source,
|
||||
)
|
||||
|
||||
|
||||
def _telegram_to_response(cfg: TelegramConfig, policy: NotificationPolicyConfig) -> TelegramSettingsResponse:
|
||||
return TelegramSettingsResponse(
|
||||
enabled=cfg.enabled,
|
||||
configured=cfg.configured,
|
||||
chat_id_hint=_mask_secret(cfg.chat_id),
|
||||
bot_token_hint=_mask_secret(cfg.bot_token),
|
||||
min_severity=cfg.min_severity,
|
||||
min_severity=policy.min_severity,
|
||||
source=cfg.source,
|
||||
)
|
||||
|
||||
|
||||
def _webhook_to_response(cfg: WebhookConfig) -> WebhookSettingsResponse:
|
||||
def _webhook_to_response(cfg: WebhookConfig, policy: NotificationPolicyConfig) -> WebhookSettingsResponse:
|
||||
return WebhookSettingsResponse(
|
||||
enabled=cfg.enabled,
|
||||
configured=cfg.configured,
|
||||
url_hint=_mask_url(cfg.url),
|
||||
secret_header=cfg.secret_header or None,
|
||||
secret_hint=_mask_secret(cfg.secret),
|
||||
min_severity=cfg.min_severity,
|
||||
min_severity=policy.min_severity,
|
||||
source=cfg.source,
|
||||
)
|
||||
|
||||
|
||||
def _email_to_response(cfg: EmailConfig) -> EmailSettingsResponse:
|
||||
def _email_to_response(cfg: EmailConfig, policy: NotificationPolicyConfig) -> EmailSettingsResponse:
|
||||
return EmailSettingsResponse(
|
||||
enabled=cfg.enabled,
|
||||
configured=cfg.configured,
|
||||
@@ -149,7 +177,7 @@ def _email_to_response(cfg: EmailConfig) -> EmailSettingsResponse:
|
||||
mail_to_hint=_mask_secret(cfg.mail_to.replace(";", ",")) if cfg.mail_to else None,
|
||||
smtp_starttls=cfg.smtp_starttls,
|
||||
smtp_ssl=cfg.smtp_ssl,
|
||||
min_severity=cfg.min_severity,
|
||||
min_severity=policy.min_severity,
|
||||
source=cfg.source,
|
||||
)
|
||||
|
||||
@@ -159,32 +187,55 @@ def get_notification_settings(
|
||||
db: Session = Depends(get_db),
|
||||
_user: str = Depends(get_current_user),
|
||||
) -> NotificationSettingsResponse:
|
||||
policy = get_effective_notification_policy(db)
|
||||
return NotificationSettingsResponse(
|
||||
telegram=_telegram_to_response(get_effective_telegram_config(db)),
|
||||
webhook=_webhook_to_response(get_effective_webhook_config(db)),
|
||||
email=_email_to_response(get_effective_email_config(db)),
|
||||
policy=_policy_to_response(policy),
|
||||
telegram=_telegram_to_response(get_effective_telegram_config(db), policy),
|
||||
webhook=_webhook_to_response(get_effective_webhook_config(db), policy),
|
||||
email=_email_to_response(get_effective_email_config(db), policy),
|
||||
)
|
||||
|
||||
|
||||
@router.put("/notifications/policy", response_model=NotificationPolicyResponse)
|
||||
def update_notification_policy(
|
||||
body: NotificationPolicyUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
_user: str = Depends(get_current_user),
|
||||
) -> NotificationPolicyResponse:
|
||||
if body.min_severity not in VALID_SEVERITIES:
|
||||
raise HTTPException(status_code=422, detail=f"min_severity must be one of: {sorted(VALID_SEVERITIES)}")
|
||||
if not any((body.use_telegram, body.use_webhook, body.use_email)):
|
||||
raise HTTPException(status_code=422, detail="Выберите хотя бы один канал")
|
||||
try:
|
||||
policy = upsert_notification_policy(
|
||||
db,
|
||||
min_severity=body.min_severity,
|
||||
use_telegram=body.use_telegram,
|
||||
use_webhook=body.use_webhook,
|
||||
use_email=body.use_email,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
return _policy_to_response(policy)
|
||||
|
||||
|
||||
@router.put("/notifications/telegram", response_model=TelegramSettingsResponse)
|
||||
def update_telegram_settings(
|
||||
body: TelegramSettingsUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
_user: str = Depends(get_current_user),
|
||||
) -> TelegramSettingsResponse:
|
||||
if body.min_severity not in VALID_SEVERITIES:
|
||||
raise HTTPException(status_code=422, detail=f"min_severity must be one of: {sorted(VALID_SEVERITIES)}")
|
||||
try:
|
||||
cfg = upsert_telegram_channel(
|
||||
db,
|
||||
enabled=body.enabled,
|
||||
min_severity=body.min_severity,
|
||||
bot_token=body.bot_token,
|
||||
chat_id=body.chat_id,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
return _telegram_to_response(cfg)
|
||||
policy = get_effective_notification_policy(db)
|
||||
return _telegram_to_response(cfg, policy)
|
||||
|
||||
|
||||
@router.put("/notifications/webhook", response_model=WebhookSettingsResponse)
|
||||
@@ -193,20 +244,18 @@ def update_webhook_settings(
|
||||
db: Session = Depends(get_db),
|
||||
_user: str = Depends(get_current_user),
|
||||
) -> WebhookSettingsResponse:
|
||||
if body.min_severity not in VALID_SEVERITIES:
|
||||
raise HTTPException(status_code=422, detail=f"min_severity must be one of: {sorted(VALID_SEVERITIES)}")
|
||||
try:
|
||||
cfg = upsert_webhook_channel(
|
||||
db,
|
||||
enabled=body.enabled,
|
||||
min_severity=body.min_severity,
|
||||
url=body.url,
|
||||
secret_header=body.secret_header,
|
||||
secret=body.secret,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
return _webhook_to_response(cfg)
|
||||
policy = get_effective_notification_policy(db)
|
||||
return _webhook_to_response(cfg, policy)
|
||||
|
||||
|
||||
@router.put("/notifications/email", response_model=EmailSettingsResponse)
|
||||
@@ -215,13 +264,10 @@ def update_email_settings(
|
||||
db: Session = Depends(get_db),
|
||||
_user: str = Depends(get_current_user),
|
||||
) -> EmailSettingsResponse:
|
||||
if body.min_severity not in VALID_SEVERITIES:
|
||||
raise HTTPException(status_code=422, detail=f"min_severity must be one of: {sorted(VALID_SEVERITIES)}")
|
||||
try:
|
||||
cfg = upsert_email_channel(
|
||||
db,
|
||||
enabled=body.enabled,
|
||||
min_severity=body.min_severity,
|
||||
smtp_host=body.smtp_host,
|
||||
smtp_port=body.smtp_port,
|
||||
smtp_user=body.smtp_user,
|
||||
@@ -233,7 +279,8 @@ def update_email_settings(
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
return _email_to_response(cfg)
|
||||
policy = get_effective_notification_policy(db)
|
||||
return _email_to_response(cfg, policy)
|
||||
|
||||
|
||||
@router.post("/notifications/telegram/test", response_model=ChannelTestResponse)
|
||||
|
||||
Reference in New Issue
Block a user