Create Development-Guide wiki page for fractafrag

xpltd_admin 2026-04-03 22:53:38 -06:00
parent 2e616d25c9
commit 845143a489

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
```