{"id":51246903,"url":"https://github.com/sibeardev/stina","last_synced_at":"2026-06-29T05:01:26.764Z","repository":{"id":365895489,"uuid":"1272170847","full_name":"sibeardev/stina","owner":"sibeardev","description":"MVP: Backend service for booking","archived":false,"fork":false,"pushed_at":"2026-06-19T10:11:44.000Z","size":82,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-19T12:11:58.413Z","etag":null,"topics":["alembic","fastapi","postgres","python3","redis","taskiq"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sibeardev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-17T10:59:45.000Z","updated_at":"2026-06-19T10:11:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sibeardev/stina","commit_stats":null,"previous_names":["sibeardev/stina"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/sibeardev/stina","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sibeardev%2Fstina","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sibeardev%2Fstina/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sibeardev%2Fstina/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sibeardev%2Fstina/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sibeardev","download_url":"https://codeload.github.com/sibeardev/stina/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sibeardev%2Fstina/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34913586,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-29T02:00:05.398Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alembic","fastapi","postgres","python3","redis","taskiq"],"created_at":"2026-06-29T05:01:26.161Z","updated_at":"2026-06-29T05:01:26.758Z","avatar_url":"https://github.com/sibeardev.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Stina\n\nBackend-сервис для записи на встречи. Пользователь создаёт бронь через REST API, система ставит задачу в очередь, воркер асинхронно подтверждает запись и логирует mock-уведомление.\n\n\u003e ⚠️ MVP. Проект реализован в рамках тестового задания.\n\n## Стек\n\n- **Python 3.14**, **FastAPI**, **SQLAlchemy 2 (async)**\n- **PostgreSQL 18**, **Redis 7**\n- **TaskIQ** + **taskiq-redis** (очередь и result backend)\n- **Alembic** (миграции), **pytest** (тесты), **uv** (зависимости)\n\nДокументация после запуска: [http://localhost:8000/docs](http://localhost:8000/docs)\n\n---\n\n## Запуск сервиса\n\n### Требования\n\n- Docker и Docker Compose\n- (опционально) [uv](https://docs.astral.sh/uv/) для локальной разработки и тестов\n\n### 1. Клонирование и окружение\n\n```bash\ngit clone https://github.com/sibeardev/stina.git\ncd stina\ncp .env.example .env\n```\n\nПеременные в `.env` (имена сервисов — из `docker-compose.yml`):\n\n```env\nPOSTGRES_DB=db\nPOSTGRES_USER=user\nPOSTGRES_PASSWORD=password\nREDIS_URL=redis://redis:6379/0\n```\n\n`POSTGRES_HOST` по умолчанию `db` — задаётся в [`app/core/config.py`](app/core/config.py) для контейнеров\n\n### 2. Запуск\n\n```bash\ndocker compose up --build\n```\n\nПоднимаются сервисы:\n\n- **api** — FastAPI на порту `8000`\n- **worker** — TaskIQ-воркер\n- **db** — PostgreSQL\n- **redis** — брокер и result backend\n\n### 3. Миграции\n\nПри старте API автоматически выполняется `alembic upgrade head` (с повторными попытками, пока Postgres не готов).\n\nРучной запуск при необходимости:\n\n```bash\ndocker compose exec api uv run alembic upgrade head\n```\n\n### 4. Пример запроса\n\n```bash\ncurl -X POST http://localhost:8000/bookings \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"Иван\",\n    \"datetime\": \"2026-12-01T15:00:00+03:00\",\n    \"service_type\": \"consultation\"\n  }'\n```\n\n### 5. Остановка\n\n```bash\ndocker compose down\n```\n\nДанные Postgres сохраняются в volume `postgres_data`. Полное удаление: `docker compose down -v`.\n\n---\n\n## Тесты\n\nТесты **не требуют Docker**: используется SQLite in-memory и мок постановки задачи в очередь.\n\n```bash\nuv sync --group dev\nuv run pytest\n```\n\nС подробным выводом:\n\n```bash\nuv run pytest -v\n```\n\nПокрытие:\n\n- API: создание, список, статус, отмена, валидация и пагинация\n- воркер: подтверждение, имитация сбоя внешнего сервиса, идемпотентность\n\n---\n\n## Технические решения\n\n### FastAPI + async SQLAlchemy\n\nAPI полностью асинхронный (`asyncpg`). Согласуется с TaskIQ worker (тот же `BookingService` и та же async-сессия, без отдельного sync-слоя).\n\n### TaskIQ вместо Celery\n\nВ задании Celery указан как базовый вариант. **TaskIQ** выбран как плюс за async-first подход:\n\nПостановка задачи — в роуте **после** `commit` в `service.create()`, чтобы воркер в отдельном процессе уже видел запись в БД\n\n### Идемпотентность\n\nПовторный запуск `confirm_booking_task` с тем же `booking_id` безопасен:\n\n1. Бронь читается с **`SELECT … FOR UPDATE`** — защита от гонок между воркерами.\n2. Если записи нет или `status != pending` — **выход без изменений** (уже `confirmed` / `failed` / `cancelled`).\n3. Повторное уведомление не отправляется.\n\nТак задача переживает повторную доставку из Redis без дублей и без «отката» финального статуса.\n\n### Домен и отмена\n\n- Статусы вынесены в [`app/domain/enums.py`](app/domain/enums.py) — общий тип для API, ORM и воркера.\n- `DELETE /bookings/{id}` —  запись остаётся в истории со статусом `cancelled`.\n\n### Миграции\n\n**Alembic** + async-драйвер `asyncpg` для CLI\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsibeardev%2Fstina","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsibeardev%2Fstina","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsibeardev%2Fstina/lists"}