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
133 lines
4.6 KiB
Python
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()
|