fix: commit semantic repair changes

This commit is contained in:
2026-03-21 11:22:25 +03:00
parent 0900208c1a
commit abee05558f
272 changed files with 4603 additions and 1668 deletions

View File

@@ -1,4 +1,4 @@
# [DEF:backend.src.services.profile_service:Module]
# [DEF:profile_service:Module]
#
# @COMPLEXITY: 5
# @SEMANTICS: profile, service, validation, ownership, filtering, superset, preferences
@@ -51,36 +51,47 @@ SUPPORTED_DENSITIES = {"compact", "comfortable"}
# [DEF:ProfileValidationError:Class]
# @COMPLEXITY: 3
# @RELATION: INHERITS -> Exception
# @COMPLEXITY: 2
# @PURPOSE: Domain validation error for profile preference update requests.
class ProfileValidationError(Exception):
def __init__(self, errors: Sequence[str]):
self.errors = list(errors)
super().__init__("Profile preference validation failed")
# [/DEF:ProfileValidationError:Class]
# [DEF:EnvironmentNotFoundError:Class]
# @COMPLEXITY: 3
# @RELATION: INHERITS -> Exception
# @COMPLEXITY: 2
# @PURPOSE: Raised when environment_id from lookup request is unknown in app configuration.
class EnvironmentNotFoundError(Exception):
pass
# [/DEF:EnvironmentNotFoundError:Class]
# [DEF:ProfileAuthorizationError:Class]
# @COMPLEXITY: 3
# @RELATION: INHERITS -> Exception
# @COMPLEXITY: 2
# @PURPOSE: Raised when caller attempts cross-user preference mutation.
class ProfileAuthorizationError(Exception):
pass
# [/DEF:ProfileAuthorizationError:Class]
# [DEF:ProfileService:Class]
# @RELATION: DEPENDS_ON -> sqlalchemy.orm.Session
# @COMPLEXITY: 5
# @PURPOSE: Implements profile preference read/update flow and Superset account lookup degradation strategy.
class ProfileService:
# [DEF:__init__:Function]
# [DEF:init:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Initialize service with DB session and config manager.
# @PRE: db session is active and config_manager supports get_environments().
# @POST: Service is ready for preference persistence and lookup operations.
@@ -90,14 +101,18 @@ class ProfileService:
self.plugin_loader = plugin_loader
self.auth_repository = AuthRepository(db)
self.encryption = EncryptionManager()
# [/DEF:__init__:Function]
# [/DEF:init:Function]
# [DEF:get_my_preference:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Return current user's persisted preference or default non-configured view.
# @PRE: current_user is authenticated.
# @POST: Returned payload belongs to current_user only.
def get_my_preference(self, current_user: User) -> ProfilePreferenceResponse:
with belief_scope("ProfileService.get_my_preference", f"user_id={current_user.id}"):
with belief_scope(
"ProfileService.get_my_preference", f"user_id={current_user.id}"
):
logger.reflect("[REFLECT] Loading current user's dashboard preference")
preference = self._get_preference_row(current_user.id)
security_summary = self._build_security_summary(current_user)
@@ -112,17 +127,23 @@ class ProfileService:
return ProfilePreferenceResponse(
status="success",
message="Preference loaded",
preference=self._to_preference_payload(preference, str(current_user.id)),
preference=self._to_preference_payload(
preference, str(current_user.id)
),
security=security_summary,
)
# [/DEF:get_my_preference:Function]
# [DEF:get_dashboard_filter_binding:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Return only dashboard-filter fields required by dashboards listing hot path.
# @PRE: current_user is authenticated.
# @POST: Returns normalized username and profile-default filter toggles without security summary expansion.
def get_dashboard_filter_binding(self, current_user: User) -> dict:
with belief_scope("ProfileService.get_dashboard_filter_binding", f"user_id={current_user.id}"):
with belief_scope(
"ProfileService.get_dashboard_filter_binding", f"user_id={current_user.id}"
):
preference = self._get_preference_row(current_user.id)
if preference is None:
return {
@@ -133,8 +154,12 @@ class ProfileService:
}
return {
"superset_username": self._sanitize_username(preference.superset_username),
"superset_username_normalized": self._normalize_username(preference.superset_username),
"superset_username": self._sanitize_username(
preference.superset_username
),
"superset_username_normalized": self._normalize_username(
preference.superset_username
),
"show_only_my_dashboards": bool(preference.show_only_my_dashboards),
"show_only_slug_dashboards": bool(
preference.show_only_slug_dashboards
@@ -142,9 +167,11 @@ class ProfileService:
else True
),
}
# [/DEF:get_dashboard_filter_binding:Function]
# [DEF:update_my_preference:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Validate and persist current user's profile preference in self-scoped mode.
# @PRE: current_user is authenticated and payload is provided.
# @POST: Preference row for current_user is created/updated when validation passes.
@@ -154,19 +181,29 @@ class ProfileService:
payload: ProfilePreferenceUpdateRequest,
target_user_id: Optional[str] = None,
) -> ProfilePreferenceResponse:
with belief_scope("ProfileService.update_my_preference", f"user_id={current_user.id}"):
logger.reason("[REASON] Evaluating self-scope guard before preference mutation")
with belief_scope(
"ProfileService.update_my_preference", f"user_id={current_user.id}"
):
logger.reason(
"[REASON] Evaluating self-scope guard before preference mutation"
)
requested_user_id = str(target_user_id or current_user.id)
if requested_user_id != str(current_user.id):
logger.explore("[EXPLORE] Cross-user mutation attempt blocked")
raise ProfileAuthorizationError("Cross-user preference mutation is forbidden")
raise ProfileAuthorizationError(
"Cross-user preference mutation is forbidden"
)
preference = self._get_or_create_preference_row(current_user.id)
provided_fields = set(getattr(payload, "model_fields_set", set()))
effective_superset_username = self._sanitize_username(preference.superset_username)
effective_superset_username = self._sanitize_username(
preference.superset_username
)
if "superset_username" in provided_fields:
effective_superset_username = self._sanitize_username(payload.superset_username)
effective_superset_username = self._sanitize_username(
payload.superset_username
)
effective_show_only = bool(preference.show_only_my_dashboards)
if "show_only_my_dashboards" in provided_fields:
@@ -247,12 +284,14 @@ class ProfileService:
preference.git_email = effective_git_email
if "git_personal_access_token" in provided_fields:
sanitized_token = self._sanitize_secret(payload.git_personal_access_token)
sanitized_token = self._sanitize_secret(
payload.git_personal_access_token
)
if sanitized_token is None:
preference.git_personal_access_token_encrypted = None
else:
preference.git_personal_access_token_encrypted = self.encryption.encrypt(
sanitized_token
preference.git_personal_access_token_encrypted = (
self.encryption.encrypt(sanitized_token)
)
preference.start_page = effective_start_page
@@ -263,7 +302,9 @@ class ProfileService:
preference.notify_on_fail = effective_notify_on_fail
preference.updated_at = datetime.utcnow()
persisted_preference = self.auth_repository.save_user_dashboard_preference(preference)
persisted_preference = self.auth_repository.save_user_dashboard_preference(
preference
)
logger.reason("[REASON] Preference persisted successfully")
return ProfilePreferenceResponse(
@@ -275,9 +316,11 @@ class ProfileService:
),
security=self._build_security_summary(current_user),
)
# [/DEF:update_my_preference:Function]
# [DEF:lookup_superset_accounts:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Query Superset users in selected environment and project canonical account candidates.
# @PRE: current_user is authenticated and environment_id exists.
# @POST: Returns success payload or degraded payload with warning while preserving manual fallback.
@@ -293,7 +336,9 @@ class ProfileService:
environment = self._resolve_environment(request.environment_id)
if environment is None:
logger.explore("[EXPLORE] Lookup aborted: environment not found")
raise EnvironmentNotFoundError(f"Environment '{request.environment_id}' not found")
raise EnvironmentNotFoundError(
f"Environment '{request.environment_id}' not found"
)
sort_column = str(request.sort_column or "username").strip().lower()
sort_order = str(request.sort_order or "desc").strip().lower()
@@ -338,7 +383,9 @@ class ProfileService:
items=items,
)
except Exception as exc:
logger.explore(f"[EXPLORE] Lookup degraded due to upstream error: {exc}")
logger.explore(
f"[EXPLORE] Lookup degraded due to upstream error: {exc}"
)
return SupersetAccountLookupResponse(
status="degraded",
environment_id=request.environment_id,
@@ -351,9 +398,11 @@ class ProfileService:
),
items=[],
)
# [/DEF:lookup_superset_accounts:Function]
# [DEF:matches_dashboard_actor:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Apply trim+case-insensitive actor match across owners OR modified_by.
# @PRE: bound_username can be empty; owners may contain mixed payload.
# @POST: Returns True when normalized username matches owners or modified_by.
@@ -375,9 +424,11 @@ class ProfileService:
if modified_token and normalized_actor == modified_token:
return True
return False
# [/DEF:matches_dashboard_actor:Function]
# [DEF:_build_security_summary:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Build read-only security snapshot with role and permission badges.
# @PRE: current_user is authenticated.
# @POST: Returns deterministic security projection for profile UI.
@@ -402,7 +453,9 @@ class ProfileService:
normalized_resource = self._sanitize_text(resource)
normalized_action = str(action or "").strip().upper()
if normalized_resource and normalized_action:
declared_permission_pairs.add((normalized_resource, normalized_action))
declared_permission_pairs.add(
(normalized_resource, normalized_action)
)
except Exception as discovery_error:
logger.warning(
"[ProfileService][EXPLORE] Failed to build declared permission catalog: %s",
@@ -435,13 +488,17 @@ class ProfileService:
roles=role_names,
permissions=permission_states,
)
# [/DEF:_build_security_summary:Function]
# [DEF:_collect_user_permission_pairs:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Collect effective permission tuples from current user's roles.
# @PRE: current_user can include role/permission graph.
# @POST: Returns unique normalized (resource, ACTION) tuples.
def _collect_user_permission_pairs(self, current_user: User) -> Set[Tuple[str, str]]:
def _collect_user_permission_pairs(
self, current_user: User
) -> Set[Tuple[str, str]]:
collected: Set[Tuple[str, str]] = set()
roles = getattr(current_user, "roles", []) or []
for role in roles:
@@ -452,9 +509,11 @@ class ProfileService:
if resource and action:
collected.add((resource, action))
return collected
# [/DEF:_collect_user_permission_pairs:Function]
# [DEF:_format_permission_key:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Convert normalized permission pair to compact UI key.
# @PRE: resource and action are normalized.
# @POST: Returns user-facing badge key.
@@ -464,9 +523,11 @@ class ProfileService:
if normalized_action == "READ":
return normalized_resource
return f"{normalized_resource}:{normalized_action.lower()}"
# [/DEF:_format_permission_key:Function]
# [DEF:_to_preference_payload:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Map ORM preference row to API DTO with token metadata.
# @PRE: preference row can contain nullable optional fields.
# @POST: Returns normalized ProfilePreference object.
@@ -516,13 +577,17 @@ class ProfileService:
),
telegram_id=self._sanitize_text(preference.telegram_id),
email_address=self._sanitize_text(preference.email_address),
notify_on_fail=bool(preference.notify_on_fail) if preference.notify_on_fail is not None else True,
notify_on_fail=bool(preference.notify_on_fail)
if preference.notify_on_fail is not None
else True,
created_at=created_at,
updated_at=updated_at,
)
# [/DEF:_to_preference_payload:Function]
# [DEF:_mask_secret_value:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Build a safe display value for sensitive secrets.
# @PRE: secret may be None or plaintext.
# @POST: Returns masked representation or None.
@@ -533,9 +598,11 @@ class ProfileService:
if len(sanitized_secret) <= 4:
return "***"
return f"{sanitized_secret[:2]}***{sanitized_secret[-2:]}"
# [/DEF:_mask_secret_value:Function]
# [DEF:_sanitize_text:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Normalize optional text into trimmed form or None.
# @PRE: value may be empty or None.
# @POST: Returns trimmed value or None.
@@ -544,9 +611,11 @@ class ProfileService:
if not normalized:
return None
return normalized
# [/DEF:_sanitize_text:Function]
# [DEF:_sanitize_secret:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Normalize secret input into trimmed form or None.
# @PRE: value may be None or blank.
# @POST: Returns trimmed secret or None.
@@ -557,9 +626,11 @@ class ProfileService:
if not normalized:
return None
return normalized
# [/DEF:_sanitize_secret:Function]
# [DEF:_normalize_start_page:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Normalize supported start page aliases to canonical values.
# @PRE: value may be None or alias.
# @POST: Returns one of SUPPORTED_START_PAGES.
@@ -570,9 +641,11 @@ class ProfileService:
if normalized in SUPPORTED_START_PAGES:
return normalized
return "dashboards"
# [/DEF:_normalize_start_page:Function]
# [DEF:_normalize_density:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Normalize supported density aliases to canonical values.
# @PRE: value may be None or alias.
# @POST: Returns one of SUPPORTED_DENSITIES.
@@ -583,9 +656,11 @@ class ProfileService:
if normalized in SUPPORTED_DENSITIES:
return normalized
return "comfortable"
# [/DEF:_normalize_density:Function]
# [DEF:_resolve_environment:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Resolve environment model from configured environments by id.
# @PRE: environment_id is provided.
# @POST: Returns environment object when found else None.
@@ -595,17 +670,21 @@ class ProfileService:
if str(getattr(env, "id", "")) == str(environment_id):
return env
return None
# [/DEF:_resolve_environment:Function]
# [DEF:_get_preference_row:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Return persisted preference row for user or None.
# @PRE: user_id is provided.
# @POST: Returns matching row or None.
def _get_preference_row(self, user_id: str) -> Optional[UserDashboardPreference]:
return self.auth_repository.get_user_dashboard_preference(str(user_id))
# [/DEF:_get_preference_row:Function]
# [DEF:_get_or_create_preference_row:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Return existing preference row or create new unsaved row.
# @PRE: user_id is provided.
# @POST: Returned row always contains user_id.
@@ -614,9 +693,11 @@ class ProfileService:
if existing is not None:
return existing
return UserDashboardPreference(user_id=str(user_id))
# [/DEF:_get_or_create_preference_row:Function]
# [DEF:_build_default_preference:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Build non-persisted default preference DTO for unconfigured users.
# @PRE: user_id is provided.
# @POST: Returns ProfilePreference with disabled toggle and empty username.
@@ -641,9 +722,11 @@ class ProfileService:
created_at=now,
updated_at=now,
)
# [/DEF:_build_default_preference:Function]
# [DEF:_validate_update_payload:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Validate username/toggle constraints for preference mutation.
# @PRE: payload is provided.
# @POST: Returns validation errors list; empty list means valid.
@@ -664,7 +747,9 @@ class ProfileService:
"Username should not contain spaces. Please enter a valid Apache Superset username."
)
if show_only_my_dashboards and not sanitized_username:
errors.append("Superset username is required when default filter is enabled.")
errors.append(
"Superset username is required when default filter is enabled."
)
sanitized_git_email = self._sanitize_text(git_email)
if sanitized_git_email:
@@ -693,17 +778,21 @@ class ProfileService:
errors.append("Notification email should be a valid email address.")
return errors
# [/DEF:_validate_update_payload:Function]
# [DEF:_sanitize_username:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Normalize raw username into trimmed form or None for empty input.
# @PRE: value can be empty or None.
# @POST: Returns trimmed username or None.
def _sanitize_username(self, value: Optional[str]) -> Optional[str]:
return self._sanitize_text(value)
# [/DEF:_sanitize_username:Function]
# [DEF:_normalize_username:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Apply deterministic trim+lower normalization for actor matching.
# @PRE: value can be empty or None.
# @POST: Returns lowercase normalized token or None.
@@ -712,9 +801,11 @@ class ProfileService:
if sanitized is None:
return None
return sanitized.lower()
# [/DEF:_normalize_username:Function]
# [DEF:_normalize_owner_tokens:Function]
# @RELATION: BINDS_TO -> ProfileService
# @PURPOSE: Normalize owners payload into deduplicated lower-cased tokens.
# @PRE: owners can be iterable of scalars or dict-like values.
# @POST: Returns list of unique normalized owner tokens.
@@ -727,8 +818,12 @@ class ProfileService:
if isinstance(owner, dict):
first_name = self._sanitize_username(str(owner.get("first_name") or ""))
last_name = self._sanitize_username(str(owner.get("last_name") or ""))
full_name = " ".join(part for part in [first_name, last_name] if part).strip()
snake_name = "_".join(part for part in [first_name, last_name] if part).strip("_")
full_name = " ".join(
part for part in [first_name, last_name] if part
).strip()
snake_name = "_".join(
part for part in [first_name, last_name] if part
).strip("_")
owner_candidates = [
owner.get("username"),
owner.get("user_name"),
@@ -748,7 +843,10 @@ class ProfileService:
if token and token not in normalized:
normalized.append(token)
return normalized
# [/DEF:_normalize_owner_tokens:Function]
# [/DEF:ProfileService:Class]
# [/DEF:backend.src.services.profile_service:Module]
# [/DEF:profile_service:Module]