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