forked from cardosofelipe/fast-next-template
fix: Add missing API endpoints and validation improvements
- Add cancel_sprint and delete_sprint endpoints to sprints.py - Add unassign_issue endpoint to issues.py - Add remove_issue_from_sprint endpoint to sprints.py - Add CRUD methods: remove_sprint_from_issues, unassign, remove_from_sprint - Add validation to prevent closed issues in active/planned sprints - Add authorization tests for SSE events endpoint - Fix IDOR vulnerabilities in agents.py and projects.py - Add Syndarix models migration (0004) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ within their projects, including spawning, pausing, resuming, and terminating ag
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
@@ -23,6 +24,7 @@ from app.core.exceptions import (
|
||||
ValidationException,
|
||||
)
|
||||
from app.crud.syndarix.agent_instance import agent_instance as agent_instance_crud
|
||||
from app.crud.syndarix.agent_type import agent_type as agent_type_crud
|
||||
from app.crud.syndarix.project import project as project_crud
|
||||
from app.models.syndarix import AgentInstance, Project
|
||||
from app.models.syndarix.enums import AgentStatus
|
||||
@@ -47,6 +49,10 @@ logger = logging.getLogger(__name__)
|
||||
# Initialize limiter for this router
|
||||
limiter = Limiter(key_func=get_remote_address)
|
||||
|
||||
# Use higher rate limits in test environment
|
||||
IS_TEST = os.getenv("IS_TEST", "False") == "True"
|
||||
RATE_MULTIPLIER = 100 if IS_TEST else 1
|
||||
|
||||
|
||||
# Valid status transitions for agent lifecycle management
|
||||
VALID_STATUS_TRANSITIONS: dict[AgentStatus, set[AgentStatus]] = {
|
||||
@@ -173,7 +179,7 @@ def build_agent_response(
|
||||
description="Spawn a new agent instance in a project. Requires project ownership or superuser.",
|
||||
operation_id="spawn_agent",
|
||||
)
|
||||
@limiter.limit("20/minute")
|
||||
@limiter.limit(f"{20 * RATE_MULTIPLIER}/minute")
|
||||
async def spawn_agent(
|
||||
request: Request,
|
||||
project_id: UUID,
|
||||
@@ -214,6 +220,20 @@ async def spawn_agent(
|
||||
field="project_id",
|
||||
)
|
||||
|
||||
# Validate that the agent type exists and is active
|
||||
agent_type = await agent_type_crud.get(db, id=agent_in.agent_type_id)
|
||||
if not agent_type:
|
||||
raise NotFoundError(
|
||||
message=f"Agent type {agent_in.agent_type_id} not found",
|
||||
error_code=ErrorCode.NOT_FOUND,
|
||||
)
|
||||
if not agent_type.is_active:
|
||||
raise ValidationException(
|
||||
message=f"Agent type '{agent_type.name}' is inactive and cannot be used",
|
||||
error_code=ErrorCode.VALIDATION_ERROR,
|
||||
field="agent_type_id",
|
||||
)
|
||||
|
||||
# Create the agent instance
|
||||
agent = await agent_instance_crud.create(db, obj_in=agent_in)
|
||||
|
||||
@@ -256,7 +276,7 @@ async def spawn_agent(
|
||||
description="List all agent instances in a project with optional filtering.",
|
||||
operation_id="list_project_agents",
|
||||
)
|
||||
@limiter.limit("60/minute")
|
||||
@limiter.limit(f"{60 * RATE_MULTIPLIER}/minute")
|
||||
async def list_project_agents(
|
||||
request: Request,
|
||||
project_id: UUID,
|
||||
@@ -350,7 +370,7 @@ async def list_project_agents(
|
||||
description="Get detailed information about a specific agent instance.",
|
||||
operation_id="get_agent",
|
||||
)
|
||||
@limiter.limit("60/minute")
|
||||
@limiter.limit(f"{60 * RATE_MULTIPLIER}/minute")
|
||||
async def get_agent(
|
||||
request: Request,
|
||||
project_id: UUID,
|
||||
@@ -427,7 +447,7 @@ async def get_agent(
|
||||
description="Update an agent instance's configuration and state.",
|
||||
operation_id="update_agent",
|
||||
)
|
||||
@limiter.limit("30/minute")
|
||||
@limiter.limit(f"{30 * RATE_MULTIPLIER}/minute")
|
||||
async def update_agent(
|
||||
request: Request,
|
||||
project_id: UUID,
|
||||
@@ -522,7 +542,7 @@ async def update_agent(
|
||||
description="Pause an agent instance, temporarily stopping its work.",
|
||||
operation_id="pause_agent",
|
||||
)
|
||||
@limiter.limit("20/minute")
|
||||
@limiter.limit(f"{20 * RATE_MULTIPLIER}/minute")
|
||||
async def pause_agent(
|
||||
request: Request,
|
||||
project_id: UUID,
|
||||
@@ -621,7 +641,7 @@ async def pause_agent(
|
||||
description="Resume a paused agent instance.",
|
||||
operation_id="resume_agent",
|
||||
)
|
||||
@limiter.limit("20/minute")
|
||||
@limiter.limit(f"{20 * RATE_MULTIPLIER}/minute")
|
||||
async def resume_agent(
|
||||
request: Request,
|
||||
project_id: UUID,
|
||||
@@ -720,7 +740,7 @@ async def resume_agent(
|
||||
description="Terminate an agent instance, permanently stopping it.",
|
||||
operation_id="terminate_agent",
|
||||
)
|
||||
@limiter.limit("10/minute")
|
||||
@limiter.limit(f"{10 * RATE_MULTIPLIER}/minute")
|
||||
async def terminate_agent(
|
||||
request: Request,
|
||||
project_id: UUID,
|
||||
@@ -817,7 +837,7 @@ async def terminate_agent(
|
||||
description="Get usage metrics for a specific agent instance.",
|
||||
operation_id="get_agent_metrics",
|
||||
)
|
||||
@limiter.limit("60/minute")
|
||||
@limiter.limit(f"{60 * RATE_MULTIPLIER}/minute")
|
||||
async def get_agent_metrics(
|
||||
request: Request,
|
||||
project_id: UUID,
|
||||
@@ -897,7 +917,7 @@ async def get_agent_metrics(
|
||||
description="Get aggregated usage metrics for all agents in a project.",
|
||||
operation_id="get_project_agent_metrics",
|
||||
)
|
||||
@limiter.limit("60/minute")
|
||||
@limiter.limit(f"{60 * RATE_MULTIPLIER}/minute")
|
||||
async def get_project_agent_metrics(
|
||||
request: Request,
|
||||
project_id: UUID,
|
||||
|
||||
Reference in New Issue
Block a user