Create Development-Guide wiki page for fractafrag
parent
2e616d25c9
commit
845143a489
1 changed files with 229 additions and 0 deletions
229
Development-Guide.-.md
Normal file
229
Development-Guide.-.md
Normal file
|
|
@ -0,0 +1,229 @@
|
||||||
|
# Development Guide
|
||||||
|
|
||||||
|
| Meta | Value |
|
||||||
|
|------|-------|
|
||||||
|
| **Repo** | `xpltdco/fractafrag` |
|
||||||
|
| **Page** | `Development-Guide` |
|
||||||
|
| **Audience** | developers, agents |
|
||||||
|
| **Last Updated** | 2026-04-04 |
|
||||||
|
| **Status** | current |
|
||||||
|
|
||||||
|
## Code Organization Patterns
|
||||||
|
|
||||||
|
### Adding a New API Endpoint
|
||||||
|
|
||||||
|
1. **Create/edit router** in `services/api/app/routers/` — define the FastAPI route
|
||||||
|
2. **Add Pydantic schemas** in `services/api/app/schemas/schemas.py` — request/response models
|
||||||
|
3. **Register router** in `services/api/app/main.py` (if new file): `app.include_router(router, prefix="/api/v1/...")`
|
||||||
|
4. **Add auth dependency** if needed: `current_user: User = Depends(get_current_user)`
|
||||||
|
5. **Database access** via async session: `db: AsyncSession = Depends(get_db)`
|
||||||
|
6. **Add frontend API call** in the React frontend via Axios or React Query hook
|
||||||
|
|
||||||
|
### Adding a New Database Table
|
||||||
|
|
||||||
|
1. **Add SQL** to `db/init.sql` (for fresh installs)
|
||||||
|
2. **Add ORM model** in `services/api/app/models/models.py` using SQLAlchemy declarative
|
||||||
|
3. **Create Alembic migration** for existing databases:
|
||||||
|
```bash
|
||||||
|
docker compose exec api alembic revision --autogenerate -m "add my_table"
|
||||||
|
docker compose exec api alembic upgrade head
|
||||||
|
```
|
||||||
|
4. **Add Pydantic schemas** for the new model
|
||||||
|
|
||||||
|
### Adding a Celery Task
|
||||||
|
|
||||||
|
1. **Define task** in `services/api/app/worker/__init__.py`
|
||||||
|
2. **Use sync DB session** (Celery runs in sync context — use `psycopg2`, not `asyncpg`)
|
||||||
|
3. **Add retry logic** if the task can fail transiently: `@celery_app.task(bind=True, max_retries=3)`
|
||||||
|
4. **Enqueue from API** router: `from app.worker import my_task; my_task.delay(args)`
|
||||||
|
5. **Add periodic schedule** if needed (Beat schedule in worker `__init__.py`)
|
||||||
|
|
||||||
|
### Adding an MCP Tool
|
||||||
|
|
||||||
|
1. **Add tool function** in `services/mcp/server.py` with `@mcp.tool()` decorator
|
||||||
|
2. **Call the API** internally via httpx: `POST http://api:8000/api/v1/...`
|
||||||
|
3. **Return structured data** (dict with clear field names for agent consumption)
|
||||||
|
|
||||||
|
### Adding a Frontend Page
|
||||||
|
|
||||||
|
1. **Create page component** in `services/frontend/src/pages/`
|
||||||
|
2. **Add route** in the React Router configuration
|
||||||
|
3. **Create API hooks** using TanStack Query: `useQuery`, `useMutation`
|
||||||
|
4. **Style with Tailwind** CSS utility classes
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Test Framework
|
||||||
|
|
||||||
|
**pytest + pytest-asyncio** for the API backend.
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Via Docker (recommended)
|
||||||
|
make test
|
||||||
|
# or: docker compose exec api python -m pytest tests/ -v
|
||||||
|
|
||||||
|
# Locally (requires dev deps)
|
||||||
|
cd services/api
|
||||||
|
pip install -e ".[dev]"
|
||||||
|
pytest tests/ -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
services/api/tests/
|
||||||
|
├── conftest.py # Fixtures (async client, test DB)
|
||||||
|
├── test_auth.py # Auth endpoint tests
|
||||||
|
├── test_shaders.py # Shader CRUD tests
|
||||||
|
├── test_feed.py # Feed ranking tests
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Tests use an in-memory SQLite database (via `aiosqlite`) for speed. The `conftest.py` sets up a test FastAPI client with overridden database dependency.
|
||||||
|
|
||||||
|
### Writing Tests
|
||||||
|
|
||||||
|
```python
|
||||||
|
import pytest
|
||||||
|
from httpx import AsyncClient
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_shader(client: AsyncClient, auth_headers: dict):
|
||||||
|
response = await client.post(
|
||||||
|
"/api/v1/shaders",
|
||||||
|
json={
|
||||||
|
"title": "Test Shader",
|
||||||
|
"glsl_code": "void mainImage(out vec4 f, in vec2 c) { f = vec4(1.0); }",
|
||||||
|
"shader_type": "2d",
|
||||||
|
"status": "draft"
|
||||||
|
},
|
||||||
|
headers=auth_headers
|
||||||
|
)
|
||||||
|
assert response.status_code == 201
|
||||||
|
assert response.json()["title"] == "Test Shader"
|
||||||
|
```
|
||||||
|
|
||||||
|
## CI/CD Pipeline
|
||||||
|
|
||||||
|
### Forgejo Actions
|
||||||
|
|
||||||
|
**Workflow:** `.forgejo/workflows/ci.yml`
|
||||||
|
|
||||||
|
**Triggers:** Push or PR to `master` branch.
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Checkout code
|
||||||
|
2. Install Python deps: `pip install -e ".[dev]"` (from `services/api/`)
|
||||||
|
3. Lint: `ruff check app/ tests/` (continue on error)
|
||||||
|
4. Tests: `pytest tests/ -v`
|
||||||
|
|
||||||
|
**Working directory:** `services/api/`
|
||||||
|
|
||||||
|
### Local CI Simulation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd services/api
|
||||||
|
ruff check app/ tests/
|
||||||
|
pytest tests/ -v
|
||||||
|
```
|
||||||
|
|
||||||
|
## Coding Conventions
|
||||||
|
|
||||||
|
### Python (API)
|
||||||
|
|
||||||
|
- **Async-first** — all route handlers and DB access use `async`/`await`
|
||||||
|
- **Type hints** throughout — function signatures, return types, Pydantic models
|
||||||
|
- **Pydantic v2** for all request/response serialization
|
||||||
|
- **SQLAlchemy 2** declarative models with async sessions
|
||||||
|
- **Dependency injection** via FastAPI `Depends()` for auth, DB, Redis
|
||||||
|
- **Linter:** ruff (configured in pyproject.toml)
|
||||||
|
|
||||||
|
### TypeScript (Frontend)
|
||||||
|
|
||||||
|
- **React 18** with functional components and hooks
|
||||||
|
- **TanStack Query** for all API data (no manual fetch/useEffect)
|
||||||
|
- **Zustand** for client-only state (auth, UI preferences)
|
||||||
|
- **Tailwind CSS** for styling (no custom CSS files unless necessary)
|
||||||
|
- **Axios** for HTTP client (configured with base URL and interceptors)
|
||||||
|
|
||||||
|
### File Naming
|
||||||
|
|
||||||
|
- **Python:** snake_case for files and modules (`glsl_validator.py`)
|
||||||
|
- **TypeScript:** PascalCase for components (`ShaderCanvas.tsx`), camelCase for utilities
|
||||||
|
- **Docker:** service names are lowercase (`api`, `mcp`, `renderer`)
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
### Hot Reload
|
||||||
|
|
||||||
|
With `docker-compose.override.yml` (applied automatically):
|
||||||
|
- **API:** `uvicorn --reload` watches `services/api/app/` for Python changes
|
||||||
|
- **Frontend:** Vite HMR for instant browser updates
|
||||||
|
- **MCP:** Volume mount for live changes (manual restart needed)
|
||||||
|
- **Worker:** Requires manual restart after task changes: `docker compose restart worker`
|
||||||
|
|
||||||
|
### Database Shell
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make db-shell
|
||||||
|
# or: docker compose exec postgres psql -U fracta -d fractafrag
|
||||||
|
|
||||||
|
# Useful queries
|
||||||
|
\dt -- List tables
|
||||||
|
\d shaders -- Describe table
|
||||||
|
SELECT COUNT(*) FROM shaders; -- Count shaders
|
||||||
|
SELECT * FROM system_config; -- App settings (if any)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Redis Shell
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec redis redis-cli
|
||||||
|
|
||||||
|
# Useful commands
|
||||||
|
KEYS * -- List all keys
|
||||||
|
KEYS blocklist:* -- List blocklisted tokens
|
||||||
|
TTL blocklist:some-token -- Check token expiry
|
||||||
|
INFO memory -- Memory usage
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Testing with curl
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Register
|
||||||
|
curl -X POST http://localhost/api/v1/auth/register \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"test","email":"test@test.com","password":"test1234"}'
|
||||||
|
|
||||||
|
# Login (save token)
|
||||||
|
TOKEN=$(curl -s -X POST http://localhost/api/v1/auth/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"test","password":"test1234"}' | jq -r .access_token)
|
||||||
|
|
||||||
|
# Create shader
|
||||||
|
curl -X POST http://localhost/api/v1/shaders \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"title":"Test","glsl_code":"void mainImage(out vec4 f,in vec2 c){f=vec4(1.);}","shader_type":"2d","status":"draft"}'
|
||||||
|
|
||||||
|
# Browse feed
|
||||||
|
curl http://localhost/api/v1/feed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debugging Celery Tasks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View worker logs
|
||||||
|
docker compose logs -f worker
|
||||||
|
|
||||||
|
# Start worker with debug logging
|
||||||
|
docker compose exec worker python -m celery -A app.worker worker --loglevel=debug
|
||||||
|
|
||||||
|
# Inspect registered tasks
|
||||||
|
docker compose exec worker python -m celery -A app.worker inspect registered
|
||||||
|
|
||||||
|
# Purge all pending tasks
|
||||||
|
docker compose exec worker python -m celery -A app.worker purge
|
||||||
|
```
|
||||||
Loading…
Add table
Reference in a new issue