---
description: Overall project architecture and context for duty-teller
globs:
  - "**"
---

# Project — duty-teller

A Telegram bot for team duty shift calendar management and group reminders,
with a Telegram Mini App (webapp) for calendar visualization.

## Architecture

```
┌──────────────┐   polling    ┌──────────────────────┐
│  Telegram    │◄────────────►│  python-telegram-bot  │
│  Bot API     │              │  (handlers/)          │
└──────────────┘              └──────────┬───────────┘
                                         │
┌──────────────┐   HTTP       ┌──────────▼───────────┐
│  Telegram    │◄────────────►│  FastAPI (api/)       │
│  Mini App    │  initData    │  + static webapp      │
│  (webapp-next/) │  auth     └──────────┬───────────┘
└──────────────┘                         │
                              ┌──────────▼───────────┐
                              │  SQLite + SQLAlchemy  │
                              │  (db/)                │
                              └──────────────────────┘
```

- **Bot:** python-telegram-bot v22, async polling mode.
- **API:** FastAPI served by uvicorn in a daemon thread alongside the bot.
- **Database:** SQLite via SQLAlchemy 2.x ORM; Alembic for migrations.
- **Frontend:** Next.js (TypeScript, Tailwind, shadcn/ui) static export at `/app`; source in `webapp-next/`.

## Key packages

| Package | Role |
|---------|------|
| `duty_teller/handlers/` | Telegram bot command and message handlers |
| `duty_teller/api/` | FastAPI app, REST endpoints, Telegram initData auth, ICS feeds |
| `duty_teller/db/` | ORM models, repository (CRUD), session management, Pydantic schemas |
| `duty_teller/services/` | Business logic (import, duty pin) — receives Session |
| `duty_teller/importers/` | Duty schedule file parsers |
| `duty_teller/i18n/` | Bilingual translations (ru/en), `t()` function |
| `duty_teller/utils/` | Date helpers, user utilities, HTTP client |
| `duty_teller/cache.py` | TTL caches with pattern-based invalidation |
| `duty_teller/config.py` | Environment-based configuration |
| `webapp-next/` | Telegram Mini App (Next.js, Tailwind, shadcn/ui; build → `out/`) |

## API endpoints

| Method | Path | Auth | Purpose |
|--------|------|------|---------|
| GET | `/health` | None | Health check |
| GET | `/api/duties` | initData | Duties for date range |
| GET | `/api/calendar-events` | initData | External calendar events |
| GET | `/api/calendar/ical/team/{token}.ics` | Token | Team ICS feed |
| GET | `/api/calendar/ical/{token}.ics` | Token | Personal ICS feed |
| GET | `/app` | None | Static Mini App (HTML/JS/CSS) |

## Deployment

- **Docker:** Multi-stage build (`Dockerfile`), `docker-compose.prod.yml` for production.
- **Entrypoint:** `entrypoint.sh` runs `alembic -c pyproject.toml upgrade head`, then
  starts `python main.py` as `botuser`.
- **Data:** SQLite DB persisted via Docker volume at `/app/data`.
- **Health:** `curl -f http://localhost:8080/health` every 30 s.

## CI/CD (Gitea Actions)

### Lint & test (`.gitea/workflows/ci.yml`)

Triggered on push/PR to `main` and `develop`:

1. **Ruff:** `ruff check duty_teller tests`
2. **Pytest:** `pytest tests/ -v` (80% coverage gate)
3. **Bandit:** `bandit -r duty_teller -ll` (security scan)

### Docker build & release (`.gitea/workflows/docker-build.yml`)

Triggered on `v*` tags:

1. Build and push image to Gitea registry.
2. Create release with auto-generated notes.

## Key environment variables

| Variable | Default | Required | Purpose |
|----------|---------|----------|---------|
| `BOT_TOKEN` | — | Yes | Telegram bot API token |
| `DATABASE_URL` | `sqlite:///data/duty_teller.db` | No | SQLAlchemy database URL |
| `MINI_APP_BASE_URL` | — | Yes (prod) | Public URL for the Mini App |
| `HTTP_HOST` | `127.0.0.1` | No | FastAPI bind host |
| `HTTP_PORT` | `8080` | No | FastAPI bind port |
| `ADMIN_USERNAMES` | — | No | Comma-separated admin Telegram usernames |
| `ALLOWED_USERNAMES` | — | No | Comma-separated allowed usernames |
| `DUTY_DISPLAY_TZ` | `Europe/Moscow` | No | Timezone for duty display |
| `DEFAULT_LANGUAGE` | `en` | No | Default UI language (`en` or `ru`) |
| `EXTERNAL_CALENDAR_ICS_URL` | — | No | External ICS URL for holidays |
| `CORS_ORIGINS` | `*` | No | Comma-separated CORS origins |

## Languages

- **Backend:** Python 3.12+
- **Frontend:** Next.js (TypeScript), Tailwind CSS, shadcn/ui; Vitest for tests
- **i18n:** Russian (default) and English

## Version control

- Git with Gitea Flow branching strategy.
- Conventional Commits for commit messages.
- PRs reviewed via Gitea Pull Requests.
