DealDocumentScreening/Makefile

284 lines
16 KiB
Makefile

# «Контракт-чек» — everyday commands (uv + docker)
# Usage: make <target> (see `make help`)
.PHONY: help install lint lint-fix isort isort-check typecheck test test-unit test-integration migrate \
infra-up infra-down infra-logs services-up services-down services-logs \
services-obs obs-up obs-down obs-logs obs-url obs-reset \
api api-logs bot bot-logs worker-extract worker-analyze worker-notify \
seed-token jwt-secret jwt-token jwt-verify health shell-api shell-bot \
shell-db admin-promote admin-list clean
# ─────────────────────────────────────────────────────────────────────────────
# Help
# ─────────────────────────────────────────────────────────────────────────────
help: ## Show available commands
@echo "Usage: make <target>"
@grep -E '^[a-zA-Z0-9_-]+:.*##' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*##"}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}'
# ─────────────────────────────────────────────────────────────────────────────
# Local dev (uv)
# ─────────────────────────────────────────────────────────────────────────────
install: ## Sync dev dependencies (uv)
uv sync --group dev
lint: ## Run ruff linter + format check
uv run ruff check src tests
uv run ruff format --check src tests
lint-fix: ## Run ruff autofix + format + isort fix
uv run ruff check --fix src tests
uv run ruff format src tests
uv run isort src tests
isort: ## Run isort (sort imports in-place)
uv run isort src tests
isort-check: ## Run isort in check-only mode
uv run isort --check-only src tests
typecheck: ## Run ty type checker
uv run ty check src
test: ## Run all tests (unit + integration)
uv run pytest
test-unit: ## Run unit tests only (fast)
uv run pytest -m "not integration"
test-cov: ## Run unit tests with coverage report (enforces threshold)
uv run pytest --cov=src/contract_check --cov-branch --cov-report=term-missing --cov-report=html --cov-fail-under=59 -m "not integration" tests/unit
test-integration: ## Run integration tests (needs docker infra)
uv run pytest -m integration
# ─────────────────────────────────────────────────────────────────────────────
# Docker: infrastructure (postgres + redis + rabbitmq + minio)
# ─────────────────────────────────────────────────────────────────────────────
infra-up: ## Start infra containers
docker compose up -d
infra-down: ## Stop infra containers
docker compose down
infra-logs: ## Tail infra logs
docker compose logs -f
# ─────────────────────────────────────────────────────────────────────────────
# Docker: all services (api + workers + bot)
# ─────────────────────────────────────────────────────────────────────────────
services-up: ## Start all services (needs infra running)
docker compose --profile services up -d --build --remove-orphans
services-down: ## Stop all services
docker compose --profile services down
services-logs: ## Tail all service logs
docker compose --profile services logs -f
services-ps: ## Show running containers
docker compose --profile services ps
services-obs: ## Start services + Grafana/Loki/Prometheus observability
docker compose --profile services --profile obs up -d --build --remove-orphans
services-observer: ## Start services + OpenObserve collector stack
docker compose --profile services --profile observer up -d --build --remove-orphans
services-nginx: ## Start with nginx reverse proxy
docker compose --profile services --profile edge up -d --build --remove-orphans
# ─────────────────────────────────────────────────────────────────────────────
# Docker: observability (Grafana + Loki logs)
# ─────────────────────────────────────────────────────────────────────────────
obs-up: ## Start Grafana/Loki/Prometheus containers
docker compose --profile obs up -d --build --remove-orphans
obs-down: ## Stop Grafana/Loki/Prometheus containers
docker compose --profile obs down
obs-logs: ## Tail Grafana/Loki/Prometheus containers logs
docker compose --profile obs logs -f
obs-url: ## Print Grafana URL and default credentials
@echo "Direct: http://localhost:$${GRAFANA_PORT:-3000}"
@echo "Via nginx: $${GRAFANA_ROOT_URL:-http://localhost}/grafana/"
@echo "User: $${GRAFANA_ADMIN_USER:-admin}"
@echo "Password: $${GRAFANA_ADMIN_PASSWORD:-admin}"
@echo "Dashboard: $${GRAFANA_ROOT_URL:-http://localhost}/grafana/d/contract-check-logs"
# ─────────────────────────────────────────────────────────────────────────────
# Docker: OpenObserve collector stack
# ─────────────────────────────────────────────────────────────────────────────
observer-up: ## Start OpenObserve + otel-collector containers
docker compose --profile observer up -d --build --remove-orphans
observer-down: ## Stop OpenObserve + otel-collector containers
docker compose --profile observer down
observer-logs: ## Tail OpenObserve + otel-collector containers logs
docker compose --profile observer logs -f
obs-reset: ## Reset Grafana and Loki volumes (wipes dashboards/logs data)
docker compose --profile obs down -v
rm -rf deploy/observability/grafana/dashboards/*.json.tmp
# ─────────────────────────────────────────────────────────────────────────────
# Docker: nginx edge (reverse proxy)
# ─────────────────────────────────────────────────────────────────────────────
nginx-up: ## Start nginx
docker compose --profile edge up -d --build --remove-orphans
nginx-down: ## Stop nginx
docker compose --profile edge down
nginx-logs: ## Tail nginx logs
docker compose --profile edge logs -f
nginx-ps: ## Show running containers
docker compose --profile edge ps
# ─────────────────────────────────────────────────────────────────────────────
# Individual services
# ─────────────────────────────────────────────────────────────────────────────
api: ## Start/restart API service
docker compose --profile services up -d --build --remove-orphans api
api-logs: ## Tail API logs
docker compose logs -f api
bot: ## Start/restart Telegram bot (same host as the api stack)
docker compose --profile services up -d --build --remove-orphans bot
bot-logs: ## Tail bot logs (same host as the api stack)
docker compose logs -f bot
# ─────────────────────────────────────────────────────────────────────────────
# Docker: bot on a separate server (HTTP-only adapter to central API)
# ─────────────────────────────────────────────────────────────────────────────
bot-remote-up: ## Start bot on a separate server (set API_URL to central API)
docker compose -f docker-compose.bot.yml up -d --build --remove-orphans
bot-remote-down: ## Stop remote bot
docker compose -f docker-compose.bot.yml down
bot-remote-logs: ## Tail remote bot logs
docker compose -f docker-compose.bot.yml logs -f
bot-remote-ps: ## Show remote bot container status
docker compose -f docker-compose.bot.yml ps
worker-extract: ## Start/restart extract worker
docker compose --profile services up -d --build --remove-orphans worker-extract
worker-analyze: ## Start/restart analyze worker
docker compose --profile services up -d --build --remove-orphans worker-analyze
worker-analyze-logs: ## Tail analyze worker logs
docker compose logs -f worker-analyze
worker-prescreen: ## Start/restart prescreen worker
docker compose --profile services up -d --build --remove-orphans worker-prescreen
worker-prescreen-logs: ## Tail prescreen worker logs
docker compose logs -f worker-prescreen
worker-notify: ## Start/restart notify worker
docker compose --profile services up -d --build --remove-orphans worker-notify
worker-notify-logs: ## Tail notify worker logs
docker compose logs -f worker-notify
# ─────────────────────────────────────────────────────────────────────────────
# Database
# ─────────────────────────────────────────────────────────────────────────────
migrate: ## Run Alembic migrations (inside api container)
docker compose --profile services build api
docker compose --profile services run --rm api alembic upgrade head
shell-db: ## Open psql inside postgres container
docker compose exec postgres psql -U contract_check -d contract_check
# ─────────────────────────────────────────────────────────────────────────────
# Admin panel (/admin — manage users; needs role = 'admin')
# ─────────────────────────────────────────────────────────────────────────────
admin-promote: ## Grant admin role to a user (usage: make admin-promote EMAIL=a@b.c)
@if [ -z "$(EMAIL)" ]; then \
echo "Usage: make admin-promote EMAIL=a@b.c"; \
exit 1; \
fi
@docker compose exec -T postgres psql -U contract_check -d contract_check \
-c "UPDATE users SET role = 'admin' WHERE email = '$(EMAIL)';" \
-c "SELECT id, email, role FROM users WHERE email = '$(EMAIL)';"
admin-list: ## List current admin users
@docker compose exec -T postgres psql -U contract_check -d contract_check \
-c "SELECT id, email, telegram_id, role, is_active FROM users WHERE role = 'admin';"
# ─────────────────────────────────────────────────────────────────────────────
# Auth / tokens
# ─────────────────────────────────────────────────────────────────────────────
seed-token: ## Generate bot service token (prints bearer token)
docker compose --profile services exec api python -m src.contract_check.api seed-token bot-prod bot
jwt-secret: ## Generate a fresh JWT_SECRET for .env
@openssl rand -hex 32
jwt-token: ## Exchange a telegram_id for a user JWT (usage: make jwt-token TG_ID=123456)
@if [ -z "$(TG_ID)" ]; then \
echo "Usage: make jwt-token TG_ID=123456"; \
exit 1; \
fi
@TOKEN=$$(grep -E '^BOT_SERVICE_TOKEN=' .env | cut -d= -f2); \
if [ -z "$$TOKEN" ]; then \
echo "BOT_SERVICE_TOKEN not found in .env"; \
exit 1; \
fi; \
curl -s -X POST -H "Authorization: Bearer $$TOKEN" \
-H "Content-Type: application/json" \
-d '{"telegram_id":$(TG_ID)}' \
http://localhost:8000/api/v1/auth/telegram/bot | jq .
jwt-verify: ## Introspect a user JWT (usage: make jwt-verify JWT=eyJ...)
@if [ -z "$(JWT)" ]; then \
echo "Usage: make jwt-verify JWT=eyJ..."; \
exit 1; \
fi
@curl -s -H "Authorization: Bearer $(JWT)" \
http://localhost:8000/api/v1/auth/me | jq .
# ─────────────────────────────────────────────────────────────────────────────
# Health / diagnostics
# ─────────────────────────────────────────────────────────────────────────────
health: ## Check API health endpoint
@echo "API health:"
@curl -s http://localhost:8000/healthz | jq . 2>/dev/null || curl -s http://localhost:8000/healthz
@echo ""
@echo "RabbitMQ: http://localhost:15672 (guest/guest → contract_check/contract_check)"
@echo "MinIO: http://localhost:9001 (contract_check/contract_check)"
@echo "Postgres: localhost:15432 (contract_check/contract_check)"
shell-api: ## Open shell inside API container
docker compose --profile services exec api /bin/sh
shell-bot: ## Open shell inside bot container
docker compose --profile services exec bot /bin/sh
# ─────────────────────────────────────────────────────────────────────────────
# Cleanup
# ─────────────────────────────────────────────────────────────────────────────
clean: ## Remove containers, volumes, caches
docker compose --profile services down -v
docker compose down -v
rm -rf .pytest_cache .ruff_cache
uv cache clean
# ─────────────────────────────────────────────────────────────────────────────
# Full workflow shortcuts
# ─────────────────────────────────────────────────────────────────────────────
dev: install infra-up migrate services-up ## Bootstrap full dev environment
dev-obs: install infra-up migrate services-obs ## Bootstrap full dev environment with observability
stop: services-down infra-down ## Stop everything
stop-obs: obs-down services-down infra-down ## Stop everything including observability