name: stk-api description: | Generate async CRUD API endpoints for an existing stk model. Use when adding API routes, REST endpoints, or CRUD operations to an stk blueprint. argument-hint: "[ModelName]"
Generate CRUD API for $ARGUMENTS
Current state
Models available:
!grep -rn "class.*Base" stk/*/models.py 2>/dev/null
Existing API routes:
!grep -rn "@bp\.\(get\|post\|put\|delete\|patch\)\|@bp\.route" stk/*/views.py 2>/dev/null | head -30
Steps
Find the model named
$ARGUMENTSin the codebase. Read its full definition to understand fields, relationships, and existingto_dict()/from_dict().Generate these endpoints in the model's blueprint views.py:
GET /api/<plural>— List with pagination, search, filteringPOST /api/<singular>/— Create (note trailing slash)POST /api/<singular>/<id>— Update (POST, not PUT — stk convention)DELETE /api/<singular>/<id>— Delete
Every endpoint must:
- Be
async def - Use blueprint-level auth OR per-route
@auth_required("session")+@roles_required("admin") - Access DB via
await g.db_session.execute(...)orawait g.db_session.get(...) - Use
select()and.where()from sqlalchemy (NOT.filter()for new code, though existing code may use it) - Log mutations via
await Activity.register(current_user.id, "Action", data) - Wrap mutations in try/except with
await g.db_session.rollback()on error,log.exception()for logging - Return
{"message": "..."}for success/error responses
- Be
List endpoint must include:
PER_PAGE = 25constant at module levelpageandper_pagequery params- Optional
searchparam withilikeon text fields - Total count via
await g.db_session.execute(select(func.count()).select_from(Model))then.scalar() - Use
import orjson as jsonand returnResponse(json.dumps(response_data), content_type="application/json") - Response shape:
{"items": [...], "total": N, "perPage": N}
Create/Update endpoints must:
- Extract data from
{item: {...}}wrapper:json_data.get("item", {}) - For create: instantiate model, call
await instance.from_dict(data), add to session - For update: get existing, store old data, call
await instance.from_dict(data) - Use
await g.db_session.flush()before activity logging on create (to get the ID) - Return 412 on error (matching existing convention)
- Extract data from
Error responses: Return
{"message": "..."}with status codes: 400 (validation), 404 (not found), 412 (server error).If a page route doesn't exist yet, add one:
@bp.get("/<plural>/") async def <plural>_page(): return await render_template("cms/<plural>.html")Verify:
- Run
uv run ruff check --fix . && uv run ruff format . - Run
uv run python checks.py
- Run