feat(settings): Settings page with email, webhooks, notifications, profile [FASE-8]
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled

- SystemConfig model + migration b033 for runtime key-value config
- GET/PATCH /system/email-config + POST /system/email-test (admin only)
- email_service reads SMTP config from DB (overrides .env)
- Webhooks now accessible to red_lead/blue_lead + admin
- GET /users/me already existed; /users/me/preferences already working
- SettingsPage with 4 role-aware tabs:
  * Profile & Jira: jira_account_id, user info
  * Notifications: role-specific email/in-app toggles (12 prefs)
  * Webhooks: full CRUD + test ping (leads + admin)
  * Email/SMTP: enable toggle, server config, test email (admin only)
- Added /settings route (all authenticated users)
- Settings link added to Sidebar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
kitos
2026-05-19 15:10:31 +02:00
parent 93ebcf2b86
commit 0e1b8e2b39
10 changed files with 1493 additions and 24 deletions

View File

@@ -0,0 +1,43 @@
"""Phase 8: system_configs table for runtime configuration.
Revision ID: b033syscfg
Revises: b032phase7
"""
from typing import Sequence, Union
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
from alembic import op
revision: str = "b033syscfg"
down_revision: Union[str, None] = "b032phase7"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def _table_exists(name: str) -> bool:
bind = op.get_bind()
insp = sa.inspect(bind)
return name in insp.get_table_names()
def upgrade() -> None:
if not _table_exists("system_configs"):
op.create_table(
"system_configs",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column("key", sa.String(200), unique=True, nullable=False),
sa.Column("value", sa.Text, nullable=True),
sa.Column("description", sa.String(500), nullable=True),
sa.Column(
"updated_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
),
)
op.create_index("ix_system_configs_key", "system_configs", ["key"])
def downgrade() -> None:
if _table_exists("system_configs"):
op.drop_index("ix_system_configs_key", table_name="system_configs")
op.drop_table("system_configs")