feat(tempo): per-user Tempo API token — same pattern as Jira token
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Each user can now store their own personal Tempo API token in their profile settings. Time is logged using each user's own credentials. Backend: - Migration b044: adds tempo_api_token column to users table - User model: adds tempo_api_token column - UserPreferencesUpdate: adds tempo_api_token field (write-only) - UserOut: adds tempo_api_token (excluded) + tempo_token_set bool; @model_validator derives both jira_token_set and tempo_token_set - users router: handles tempo_api_token same as jira_api_token (empty string clears it, never returned in responses) - tempo_service: refactored to per-user token; has_tempo_configured(), get_user_tempo_client(user) use user.tempo_api_token; global TEMPO_ENABLED still acts as kill-switch - system router: /system/tempo-test now uses current user's personal token (any role); removed global TEMPO_API_TOKEN dependency Frontend: - settings.ts: UserPreferencesUpdate.tempo_api_token, UserMeOut.tempo_token_set - SettingsPage ProfileSection: Tempo Integration section with password field, show/hide toggle, configured badge, and Test Tempo button — mirrors the Jira token UX exactly - JiraConfigSection: removed stale global Tempo test block Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -122,16 +122,19 @@ class PasswordChange(BaseModel):
|
||||
|
||||
|
||||
class UserPreferencesUpdate(BaseModel):
|
||||
"""Payload for updating current user's notification preferences and Jira settings."""
|
||||
"""Payload for updating current user's notification preferences and Jira/Tempo settings."""
|
||||
|
||||
notification_preferences: dict | None = None
|
||||
jira_account_id: str | None = None
|
||||
# Personal Jira API token (Atlassian token) — write-only, stored encrypted at rest.
|
||||
# Personal Jira API token (Atlassian token) — write-only.
|
||||
# Set to empty string "" to clear the token.
|
||||
jira_api_token: str | None = None
|
||||
# Atlassian email for Jira auth — overrides account email.
|
||||
# Set to empty string "" to clear (falls back to account email).
|
||||
jira_email: str | None = None
|
||||
# Personal Tempo API token — write-only.
|
||||
# Set to empty string "" to clear the token.
|
||||
tempo_api_token: str | None = None
|
||||
|
||||
|
||||
class UserOut(BaseModel):
|
||||
@@ -148,20 +151,23 @@ class UserOut(BaseModel):
|
||||
notification_preferences: dict | None = None
|
||||
jira_account_id: str | None = None
|
||||
jira_email: str | None = None
|
||||
# Read from ORM but NEVER exposed in responses — used only to derive jira_token_set.
|
||||
# Read from ORM but NEVER exposed in responses — used only to derive *_token_set flags.
|
||||
jira_api_token: str | None = Field(default=None, exclude=True)
|
||||
# True when the user has a personal Atlassian token stored.
|
||||
tempo_api_token: str | None = Field(default=None, exclude=True)
|
||||
# True when the user has the respective token stored.
|
||||
jira_token_set: bool = False
|
||||
tempo_token_set: bool = False
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _derive_jira_token_set(self) -> "UserOut":
|
||||
"""Set jira_token_set from the (excluded) jira_api_token field.
|
||||
def _derive_token_set_flags(self) -> "UserOut":
|
||||
"""Derive *_token_set booleans from the (excluded) raw token fields.
|
||||
|
||||
Uses @model_validator(mode='after') so Pydantic's Rust core calls it
|
||||
during FastAPI response serialisation — model_validate() overrides are
|
||||
bypassed by FastAPI's __pydantic_validator__.validate_python() path.
|
||||
"""
|
||||
self.jira_token_set = bool(self.jira_api_token)
|
||||
self.tempo_token_set = bool(self.tempo_api_token)
|
||||
return self
|
||||
|
||||
Reference in New Issue
Block a user