164 lines
6.0 KiB
Python
164 lines
6.0 KiB
Python
"""Account Settings persistence service."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from sqlalchemy import and_, select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from ..models import Integration
|
|
from ..schemas.account_settings import (
|
|
AccountAiPreferenceSettings,
|
|
AccountNotificationPreference,
|
|
AccountNotificationSettings,
|
|
AccountProfileSettings,
|
|
AccountSettingsResponse,
|
|
AccountSettingsUpdateRequest,
|
|
)
|
|
from ..utils.timezone import now_cn_naive
|
|
|
|
INTEGRATION_TYPE = "account_settings_preferences"
|
|
|
|
|
|
class AccountSettingsService:
|
|
def __init__(self, db: AsyncSession, *, tenant_id: int) -> None:
|
|
self.db = db
|
|
self.tenant_id = int(tenant_id)
|
|
|
|
async def get_settings(self, *, user: Any) -> AccountSettingsResponse:
|
|
bundle = await self._get_bundle()
|
|
user_key = self._user_key(user)
|
|
stored = self._settings_by_user(bundle).get(user_key)
|
|
if isinstance(stored, dict):
|
|
return AccountSettingsResponse.model_validate(self._normalize_settings_payload(stored))
|
|
return self._default_settings(user=user)
|
|
|
|
async def update_settings(self, *, user: Any, body: AccountSettingsUpdateRequest) -> AccountSettingsResponse:
|
|
row, bundle = await self._get_or_init_bundle()
|
|
user_key = self._user_key(user)
|
|
settings = AccountSettingsResponse(
|
|
profile=body.profile,
|
|
aiPreferences=body.aiPreferences,
|
|
notifications=body.notifications,
|
|
saveScope="account",
|
|
updatedAt=now_cn_naive().isoformat(),
|
|
)
|
|
users = self._settings_by_user(bundle)
|
|
users[user_key] = settings.model_dump(mode="json")
|
|
bundle["users"] = users
|
|
bundle["version"] = 1
|
|
row.config = bundle
|
|
await self.db.flush()
|
|
return settings
|
|
|
|
async def _get_bundle(self) -> dict[str, Any]:
|
|
row = (
|
|
await self.db.execute(
|
|
select(Integration).where(
|
|
and_(
|
|
Integration.tenant_id == self.tenant_id,
|
|
Integration.type == INTEGRATION_TYPE,
|
|
)
|
|
)
|
|
)
|
|
).scalar_one_or_none()
|
|
if row is None:
|
|
return {"version": 1, "users": {}}
|
|
return self._normalize_bundle(row.config)
|
|
|
|
async def _get_or_init_bundle(self) -> tuple[Integration, dict[str, Any]]:
|
|
row = (
|
|
await self.db.execute(
|
|
select(Integration).where(
|
|
and_(
|
|
Integration.tenant_id == self.tenant_id,
|
|
Integration.type == INTEGRATION_TYPE,
|
|
)
|
|
)
|
|
)
|
|
).scalar_one_or_none()
|
|
if row is None:
|
|
row = Integration(
|
|
tenant_id=self.tenant_id,
|
|
type=INTEGRATION_TYPE,
|
|
is_active=True,
|
|
config={"version": 1, "users": {}},
|
|
)
|
|
self.db.add(row)
|
|
await self.db.flush()
|
|
bundle = self._normalize_bundle(row.config)
|
|
if bundle != row.config:
|
|
row.config = bundle
|
|
await self.db.flush()
|
|
return row, bundle
|
|
|
|
@staticmethod
|
|
def _normalize_bundle(config: Any) -> dict[str, Any]:
|
|
bundle = dict(config or {}) if isinstance(config, dict) else {}
|
|
if not isinstance(bundle.get("users"), dict):
|
|
bundle["users"] = {}
|
|
if not isinstance(bundle.get("version"), int):
|
|
bundle["version"] = 1
|
|
return bundle
|
|
|
|
@staticmethod
|
|
def _settings_by_user(bundle: dict[str, Any]) -> dict[str, Any]:
|
|
users = bundle.get("users")
|
|
return dict(users) if isinstance(users, dict) else {}
|
|
|
|
@staticmethod
|
|
def _normalize_settings_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
|
normalized = dict(payload)
|
|
profile = dict(normalized.get("profile") or {})
|
|
legacy_map = {
|
|
"default-avatar-01": "default-avatar-male-01",
|
|
"default-avatar-02": "default-avatar-male-02",
|
|
"default-avatar-03": "default-avatar-female-01",
|
|
}
|
|
avatar_preset = profile.get("avatarPreset")
|
|
profile["avatarPreset"] = legacy_map.get(avatar_preset, avatar_preset or "default-avatar-male-01")
|
|
normalized["profile"] = profile
|
|
return normalized
|
|
|
|
@staticmethod
|
|
def _user_key(user: Any) -> str:
|
|
return str(getattr(user, "id", "") or "unknown")
|
|
|
|
@staticmethod
|
|
def _default_settings(*, user: Any) -> AccountSettingsResponse:
|
|
role = str(getattr(user, "role", "") or "").strip()
|
|
role_title = {
|
|
"admin": "管理员",
|
|
"manager": "项目负责人",
|
|
"ADMIN": "管理员",
|
|
"MANAGER": "项目负责人",
|
|
}.get(role, role or "产品负责人")
|
|
display_name = (
|
|
str(getattr(user, "name", "") or "").strip()
|
|
or str(getattr(user, "username", "") or "").strip()
|
|
or "PMFlow 用户"
|
|
)
|
|
email = str(getattr(user, "email", "") or "").strip()
|
|
return AccountSettingsResponse(
|
|
profile=AccountProfileSettings(
|
|
displayName=display_name,
|
|
roleTitle=role_title,
|
|
email=email,
|
|
defaultLanguage="zh-CN",
|
|
avatarPreset="default-avatar-male-01",
|
|
),
|
|
aiPreferences=AccountAiPreferenceSettings(answerDepth="balanced", evidenceDisplay="inline"),
|
|
notifications=AccountNotificationSettings(
|
|
pendingConfirmation=AccountNotificationPreference(enabled=True, desktop=True, emailDigest=False),
|
|
executionWriteback=AccountNotificationPreference(enabled=True, desktop=True, emailDigest=False),
|
|
releaseGate=AccountNotificationPreference(enabled=True, desktop=True, emailDigest=True),
|
|
failureAlert=AccountNotificationPreference(enabled=True, desktop=True, emailDigest=True),
|
|
),
|
|
saveScope="account",
|
|
updatedAt=now_cn_naive().isoformat(),
|
|
)
|
|
|
|
|
|
__all__ = ["AccountSettingsService", "INTEGRATION_TYPE"]
|