mirror of
https://gitverse.ru/kpa39l/fastapi-microservice.git
synced 2026-09-29 09:15:08 +00:00
Initial commit: Hermes skill fastapi-microservice
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
---
|
||||
name: fastapi-microservice
|
||||
description: "Bootstrap FastAPI: SQLAlchemy, auth, Docker, one-pass build."
|
||||
version: 1.0.0
|
||||
author: Hermes Agent
|
||||
license: MIT
|
||||
platforms: [linux, macos]
|
||||
metadata:
|
||||
hermes:
|
||||
tags: [fastapi, microservice, bootstrap, backend, python, docker]
|
||||
related_skills: [task-list-execution, writing-plans]
|
||||
---
|
||||
|
||||
# FastAPI Microservice Bootstrap
|
||||
|
||||
## When to Use
|
||||
|
||||
The user asks to create a new FastAPI-based microservice. Signals:
|
||||
|
||||
- "создай каталог /opt/xxx, спланируем и напишем сервис"
|
||||
- "нужен сервис для X с API на FastAPI"
|
||||
- "bootstrap a new API service for Y"
|
||||
- Any request where the output is a Python/FastAPI server with routes, database, and external integration
|
||||
|
||||
## Core Rules
|
||||
|
||||
### 1. No analysis preamble — start immediately
|
||||
|
||||
The user says "создай каталог и спланируем" — your first action is to create the directory. Do not:
|
||||
|
||||
- ❌ Summarize the requirement back to them
|
||||
- ❌ Ask clarifying questions about scope
|
||||
- ❌ Propose technology choices for approval
|
||||
- ❌ Write a multi-step plan document before coding
|
||||
|
||||
✅ Instead: `mkdir -p /opt/project/app` → start writing files
|
||||
|
||||
**Investigation is part of execution, not preparation.** If you need to check Python version, installed packages, or port availability, do it as the first action of writing code, not as a preamble.
|
||||
|
||||
### 2. Build in layers, bottom-up
|
||||
|
||||
Write files in this order for maximum efficiency:
|
||||
|
||||
```
|
||||
Layer 1 — Foundation
|
||||
├── requirements.txt
|
||||
├── .env.example
|
||||
├── __init__.py files (empty, create dirs)
|
||||
├── app/config.py # pydantic-settings from env
|
||||
├── app/security.py # encryption, key generation
|
||||
├── app/database.py # async engine + session
|
||||
└── app/models.py # ORM models (User, Account, etc.)
|
||||
|
||||
Layer 2 — Business logic
|
||||
├── app/converters/ # domain-specific converters
|
||||
└── app/vk_client.py # external API clients
|
||||
|
||||
Layer 3 — API
|
||||
├── app/api/schemas.py # Pydantic request/response
|
||||
├── app/api/deps.py # auth dependencies
|
||||
└── app/api/v1.py # route handlers
|
||||
|
||||
Layer 4 — Entry point
|
||||
└── app/main.py # FastAPI app + lifespan
|
||||
|
||||
Layer 5 — Infrastructure
|
||||
├── Dockerfile
|
||||
├── docker-compose.yml
|
||||
├── Makefile
|
||||
└── .gitignore
|
||||
```
|
||||
|
||||
### 3. Generate secrets and verify immediately
|
||||
|
||||
After writing code, do NOT stop at "code is written". Do:
|
||||
|
||||
1. Generate encryption key: `python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" > secrets/token_encryption_key`
|
||||
2. Install deps: `pip install -r requirements.txt`
|
||||
3. Verify all modules import: `python3 -c "from app import ..."`
|
||||
4. Start server and test: curl health endpoint
|
||||
5. Test at least one API endpoint (e.g. convert endpoint if it's a converter service)
|
||||
|
||||
### 4. Write all project files in one pass
|
||||
|
||||
Do not save README.md and STATUS.md separately from code. Write them last, after the code compiles and runs:
|
||||
|
||||
- **README.md** — API reference, usage examples, security, run instructions
|
||||
- **STATUS.md** — implementation status table, phases, architecture diagram, how to run
|
||||
- **Dockerfile** — multi-stage, slim, read-only rootfs, non-root user
|
||||
- **docker-compose.yml** — service + volume + secret + healthcheck
|
||||
- **Makefile** — dev/install/run/test/key/user commands
|
||||
|
||||
### 5. Security by default
|
||||
|
||||
Every microservice needs:
|
||||
|
||||
- **Encrypted secrets** — Fernet (AES-128-CBC + HMAC-SHA256), key from file/secret, NOT in code
|
||||
- **API key auth** — sha256 hashed, not stored in plaintext
|
||||
- **API key ≠ external token** — the API key lets you *use* the service, not read the upstream token
|
||||
- **Minimal scope** — upstream tokens should have minimal permissions (e.g. VK wall scope only)
|
||||
- **Docker read-only rootfs** — only /data writable
|
||||
- **Non-root user** in Docker container
|
||||
|
||||
Log these in README.md as a security matrix table.
|
||||
|
||||
## Expected File Structure
|
||||
|
||||
```
|
||||
/opt/<project-name>/
|
||||
├── app/
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ ├── config.py
|
||||
│ ├── database.py
|
||||
│ ├── models.py
|
||||
│ ├── security.py
|
||||
│ ├── vk_client.py # or external_api.py
|
||||
│ ├── converters/
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── markdown_to_vk.py # or relevant converter
|
||||
│ └── api/
|
||||
│ ├── __init__.py
|
||||
│ ├── schemas.py
|
||||
│ ├── deps.py
|
||||
│ └── v1.py
|
||||
├── tests/
|
||||
│ └── (TODO after code)
|
||||
├── secrets/
|
||||
│ └── token_encryption_key # NOT in git (.gitignore it)
|
||||
├── Dockerfile
|
||||
├── docker-compose.yml
|
||||
├── requirements.txt
|
||||
├── .env.example
|
||||
├── .gitignore
|
||||
├── Makefile
|
||||
├── README.md
|
||||
└── STATUS.md
|
||||
```
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
| Pitfall | Fix |
|
||||
|---------|-----|
|
||||
| Writing README before code runs | Write docs last — they reflect what's real |
|
||||
| Asking for approval on every file | Write all files in one batch, then verify |
|
||||
| Skipping security docs | Include security matrix table in README |
|
||||
| Not testing the API after writing code | Always start server and curl at least health + one endpoint |
|
||||
| Using `&` in foreground terminal command | Use `terminal(background=True)` for server, then `curl` |
|
||||
| Hand-typing code that could be written | Batch independent file writes in the same turn |
|
||||
| Explaining the design before building | Say "Начинаю" and write files — the code IS the design |
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
After writing all code:
|
||||
|
||||
- [ ] `pip install -r requirements.txt` succeeds
|
||||
- [ ] All modules import: `python3 -c "from app import config, database, models, security, ..."`
|
||||
- [ ] Encryption key generated and stored in secrets/
|
||||
- [ ] Encrypt/decrypt round-trip works
|
||||
- [ ] API keys generate and verify
|
||||
- [ ] Converter (if any) produces correct output
|
||||
- [ ] Server starts: `uvicorn app.main:app --host 0.0.0.0 --port N`
|
||||
- [ ] `GET /api/v1/health` returns `{"status":"ok"}`
|
||||
- [ ] At least one POST endpoint responds correctly (e.g. convert or account create)
|
||||
- [ ] Swagger UI at `/docs` returns 200
|
||||
- [ ] Dockerfile builds (if docker is available)
|
||||
|
||||
## Related Skills
|
||||
|
||||
- **task-list-execution** — for executing tasks from STATUS.md files
|
||||
- **writing-plans** — for multi-step implementation plans with subagent delegation
|
||||
@@ -0,0 +1,90 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user