mirror of
https://gitverse.ru/kpa39l/fastapi-microservice.git
synced 2026-09-29 09:15:08 +00:00
90 lines
3.7 KiB
Markdown
90 lines
3.7 KiB
Markdown
# md2vk — FastAPI microservice example
|
|
|
|
**Project:** VK wall publisher — converts Markdown to VK format_data, publishes via VK API.
|
|
**Pattern:** FastAPI + async SQLAlchemy + SQLite + Fernet-encrypted tokens + external API client.
|
|
|
|
## Architecture decisions
|
|
|
|
| Decision | Choice | Why |
|
|
|----------|--------|-----|
|
|
| Database | SQLite + aiosqlite | Lightweight, no external deps, fits Docker |
|
|
| ORM | SQLAlchemy 2.0 async | Async-first, well-typed, portable |
|
|
| Token encryption | Fernet (cryptography) | AES-128-CBC + HMAC, simple API, key from file |
|
|
| Auth | API key (sha256 hash) | API key !== VK token, token never leaves encrypted storage |
|
|
| Markdown conversion | Custom parser | VK format_data is a niche JSON format, no lib exists |
|
|
| HTTP client | httpx | Async, modern, timeouts by default |
|
|
| VK API scope | wall only | Minimal permissions — can't read DMs, photos, etc. |
|
|
|
|
## Layer structure
|
|
|
|
```
|
|
app/config.py → pydantic-settings, reads TOKEN_ENCRYPTION_KEY_FILE
|
|
app/security.py → Fernet encrypt/decrypt + sha256 API key hash
|
|
app/database.py → create_async_engine, async_sessionmaker, init_db()
|
|
app/models.py → User, VkAccount, Publication (SQLAlchemy 2.0 mapped)
|
|
app/vk_client.py → VkClient class: wall.post, users.get, check_token()
|
|
app/converters/ → markdown_to_vk(): returns list of chunks with FormatItem[]
|
|
app/api/schemas.py → 12 Pydantic models (Request/Response)
|
|
app/api/deps.py → get_current_user_from_header(), get_user_by_api_key()
|
|
app/api/v1.py → 6 endpoints: health, accounts CRUD, publish, convert, publications
|
|
app/main.py → FastAPI() + lifespan (init_db on startup)
|
|
```
|
|
|
|
## Key implementation details
|
|
|
|
### Fernet key loading
|
|
```python
|
|
# app/config.py
|
|
@field_validator("fernet_key", mode="before")
|
|
@classmethod
|
|
def load_key(cls, v: Any, info: ValidationInfo) -> str:
|
|
key_file = info.data.get("token_encryption_key_file")
|
|
if key_file and Path(key_file).exists():
|
|
return Path(key_file).read_text().strip()
|
|
raise ValueError("...")
|
|
```
|
|
|
|
### VK format_data converter
|
|
The converter parses Markdown AST into VK's `format_data` JSON which uses offset-based formatting:
|
|
```json
|
|
{"version": 1, "items": [
|
|
{"type": "bold", "offset": 0, "length": 5},
|
|
{"type": "link", "offset": 12, "length": 4, "url": "https://vk.com"}
|
|
]}
|
|
```
|
|
|
|
Handles: **bold**, *italic*, `inline_code`, [links](url), #h1-######, > quotes, ```code blocks```, ---.
|
|
|
|
### API key auth dual-mode
|
|
- **Header mode**: `Authorization: Bearer <key>` — for GET/DELETE (accounts list, delete)
|
|
- **Body mode**: `api_key` field in POST body — for publish, create account, list publications
|
|
- Both call `get_user_by_api_key()` which does sha256 → query User table
|
|
|
|
## Verification commands from session
|
|
|
|
```bash
|
|
# Generate key
|
|
python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" > secrets/token_encryption_key
|
|
|
|
# Verify imports
|
|
TOKEN_ENCRYPTION_KEY_FILE=secrets/token_encryption_key \
|
|
python3 -c "from app import config, database, models, security, vk_client; from app.converters import markdown_to_vk; from app.api import schemas, deps, v1; print('OK')"
|
|
|
|
# Start server
|
|
TOKEN_ENCRYPTION_KEY_FILE=secrets/token_encryption_key \
|
|
uvicorn app.main:app --host 0.0.0.0 --port 8000
|
|
|
|
# Test
|
|
curl http://localhost:8000/api/v1/health
|
|
curl -X POST http://localhost:8000/api/v1/convert \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"message_md":"**bold** text"}'
|
|
```
|
|
|
|
## Key counts (from session)
|
|
|
|
- 9 source files (excluding __init__.py, config, and infrastructure)
|
|
- ~422 lines in converter (largest module)
|
|
- ~310 lines in API v1 router (6 endpoints)
|
|
- 3 ORM models: User, VkAccount, Publication
|
|
- 4 infrastructure files: Dockerfile, docker-compose.yml, Makefile, .gitignore |