Files
kadence/backend/app/config.py
T
timka123 1bdd65cc76
lint / ruff (push) Successful in 14s
tests / desktop (push) Successful in 22s
docker / image (push) Successful in 43s
tests / web (push) Successful in 39s
tests / android-core (push) Successful in 1m1s
tests / backend (push) Successful in 2m12s
android / apk (push) Successful in 6m50s
implement ota updates
2026-10-22 21:11:04 +03:00

133 lines
4.6 KiB
Python

"""Process-level configuration: where the database is, who may call us, and the
seed values for the integrations.
Two layers of configuration exist in this app and they do different jobs:
* **This module** — environment variables. Infrastructure plus *secrets*
(API tokens), so they can live in a `.env` file or a Docker secret and never
touch the database or the browser.
* **`app.settings_schema.AppSettings`** — the user-editable settings stored in
the database and edited in the UI (pomodoro lengths, blocked sites, report
times...). On first boot the secrets below are copied into it as defaults.
So: `JIRA_API_TOKEN` in the environment gets you a working Jira source without
ever opening the settings page, and anything you later change in the UI wins.
"""
from __future__ import annotations
import os
from functools import lru_cache
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
# Tests set this so a developer's real `.env` — with live Jira and Telegram
# credentials in it — can never leak into a test run.
_DOTENV_FILES = () if os.environ.get("KADENCE_NO_DOTENV") else (".env", "../.env")
class EnvSettings(BaseSettings):
model_config = SettingsConfigDict(
env_file=_DOTENV_FILES,
env_file_encoding="utf-8",
extra="ignore",
)
# ---- infrastructure ----------------------------------------------------
database_url: str = Field(
default="sqlite:///./kadence.db",
description="SQLAlchemy URL. Docker compose points this at Postgres.",
)
cors_origins: str = Field(
default="*",
description="Comma-separated list of allowed browser origins, or '*'.",
)
timezone: str = Field(
default="Europe/Moscow",
description="Fallback timezone for 'today' and for scheduled jobs.",
)
scheduler_enabled: bool = Field(
default=True,
description="Run the background jobs (daily report, nudges, refresh).",
)
telegram_bot_enabled: bool = Field(
default=True,
description="Run the Telegram command bot when a token is configured.",
)
# ---- Android OTA updates -----------------------------------------------
# The Gitea repository is private, so these cannot be read anonymously.
# The token is a read-only personal access token; it never leaves the
# backend, and the Android client only sees /api/updates/android.
gitea_url: str = Field(
default="https://git.timka.su",
description="Base URL of the Gitea instance that runs the Android workflow.",
)
gitea_repo: str = Field(
default="timka123/kadence",
description="owner/repository whose Actions artifacts carry the APK.",
)
gitea_token: str = Field(
default="",
description=(
"Read-only Gitea token used to read Actions artifacts. Empty disables "
"the Android update endpoint."
),
)
gitea_update_branch: str = Field(
default="master",
description="Only successful builds from this branch are offered as updates.",
)
api_token: str = Field(
default="",
description=(
"Optional shared secret. When set, every /api call must send it as "
"`X-API-Token` (the desktop app and the web UI both do)."
),
)
# ---- seeds for the editable settings -----------------------------------
jira_base_url: str = ""
jira_email: str = ""
jira_api_token: str = ""
jira_extra_jql: str = ""
jira_start_date_field: str = "customfield_10015"
google_client_id: str = ""
google_client_secret: str = ""
google_refresh_token: str = ""
google_tasks_lists: str = ""
todoist_api_token: str = ""
todoist_projects: str = ""
laundry_session: str = ""
laundry_base_url: str = "https://4ka.inbicst.ru"
telegram_bot_token: str = ""
telegram_chat_id: str = ""
slack_webhook_url: str = ""
# ---- web login (closes the browser UI; the desktop app keeps API_TOKEN) ---
auth_username: str = "timka"
auth_password: str = ""
"""Set once; the hash is seeded into settings on first boot, never stored here."""
telegram_owner_id: str = ""
"""Your numeric Telegram user id — https://t.me/userinfobot says it in a DM."""
telegram_bot_username: str = ""
"""Without the `@`. Needed to render the Telegram Login Widget."""
@property
def cors_origin_list(self) -> list[str]:
if self.cors_origins.strip() == "*":
return ["*"]
return [item.strip() for item in self.cors_origins.split(",") if item.strip()]
@lru_cache
def env_settings() -> EnvSettings:
return EnvSettings()