1 Development-Guide
xpltd_admin edited this page 2026-04-03 22:53:38 -06:00

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

# 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

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

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

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

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

# 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

# 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