refactor: fix user.id references and add missing docker imports (Task 3.4 prep)
- Fix user_id → user.id in tool_instances.py (4 occurrences) - Add get_container_status and get_container_logs imports Refs: repo-restructure Task 3.4
This commit is contained in:
@@ -4,111 +4,22 @@ import logging
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
from pydantic import BaseModel, Field, field_validator
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from src.auth.dependencies import get_current_user_id, get_db_session
|
from src.auth.dependencies import get_current_user_id, get_db_session
|
||||||
from src.models.config_folder import ConfigFolder
|
from src.models.config_folder import ConfigFolder
|
||||||
|
from src.schemas.config_folder import (
|
||||||
|
ConfigFolderCreate,
|
||||||
|
ConfigFolderUpdate,
|
||||||
|
ConfigFolderResponse,
|
||||||
|
ProjectOverrideCreate,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter(prefix="/config-folders", tags=["config-folders"])
|
router = APIRouter(prefix="/config-folders", tags=["config-folders"])
|
||||||
|
|
||||||
MAX_FOLDER_SIZE_MB = 10
|
|
||||||
MAX_FOLDER_SIZE_BYTES = MAX_FOLDER_SIZE_MB * 1024 * 1024
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigFolderCreate(BaseModel):
|
|
||||||
name: str = Field(description="Folder name (unique per user)")
|
|
||||||
description: str | None = Field(default=None, description="Optional description")
|
|
||||||
mount_path: str = Field(description="Default mount path in container")
|
|
||||||
files: dict = Field(default_factory=dict, description="Files as {path: content}")
|
|
||||||
|
|
||||||
@field_validator("mount_path")
|
|
||||||
@classmethod
|
|
||||||
def validate_mount_path(cls, v: str) -> str:
|
|
||||||
if not v.startswith("/"):
|
|
||||||
raise ValueError("Mount path must be absolute (start with /)")
|
|
||||||
return v
|
|
||||||
|
|
||||||
@field_validator("files")
|
|
||||||
@classmethod
|
|
||||||
def validate_files(cls, v: dict) -> dict:
|
|
||||||
total_size = 0
|
|
||||||
for path, content in v.items():
|
|
||||||
# Check for path traversal
|
|
||||||
if ".." in path or path.startswith("/"):
|
|
||||||
raise ValueError(f"Invalid file path: {path}")
|
|
||||||
total_size += len(content.encode("utf-8"))
|
|
||||||
|
|
||||||
if total_size > MAX_FOLDER_SIZE_BYTES:
|
|
||||||
raise ValueError(f"Total folder size exceeds {MAX_FOLDER_SIZE_MB}MB limit")
|
|
||||||
|
|
||||||
return v
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigFolderUpdate(BaseModel):
|
|
||||||
name: str | None = Field(default=None, description="Folder name")
|
|
||||||
description: str | None = Field(default=None, description="Optional description")
|
|
||||||
mount_path: str | None = Field(default=None, description="Default mount path")
|
|
||||||
files: dict | None = Field(default=None, description="Files as {path: content}")
|
|
||||||
is_active: bool | None = Field(default=None, description="Active/inactive toggle")
|
|
||||||
|
|
||||||
@field_validator("mount_path")
|
|
||||||
@classmethod
|
|
||||||
def validate_mount_path(cls, v: str | None) -> str | None:
|
|
||||||
if v is None:
|
|
||||||
return v
|
|
||||||
if not v.startswith("/"):
|
|
||||||
raise ValueError("Mount path must be absolute (start with /)")
|
|
||||||
return v
|
|
||||||
|
|
||||||
@field_validator("files")
|
|
||||||
@classmethod
|
|
||||||
def validate_files(cls, v: dict | None) -> dict | None:
|
|
||||||
if v is None:
|
|
||||||
return v
|
|
||||||
|
|
||||||
total_size = 0
|
|
||||||
for path, content in v.items():
|
|
||||||
# Check for path traversal
|
|
||||||
if ".." in path or path.startswith("/"):
|
|
||||||
raise ValueError(f"Invalid file path: {path}")
|
|
||||||
total_size += len(content.encode("utf-8"))
|
|
||||||
|
|
||||||
if total_size > MAX_FOLDER_SIZE_BYTES:
|
|
||||||
raise ValueError(f"Total folder size exceeds {MAX_FOLDER_SIZE_MB}MB limit")
|
|
||||||
|
|
||||||
return v
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectOverrideCreate(BaseModel):
|
|
||||||
mount_path: str | None = Field(default=None, description="Override mount path")
|
|
||||||
files: dict = Field(default_factory=dict, description="Override files")
|
|
||||||
|
|
||||||
@field_validator("mount_path")
|
|
||||||
@classmethod
|
|
||||||
def validate_mount_path(cls, v: str | None) -> str | None:
|
|
||||||
if v is None:
|
|
||||||
return v
|
|
||||||
if not v.startswith("/"):
|
|
||||||
raise ValueError("Mount path must be absolute (start with /)")
|
|
||||||
return v
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigFolderResponse(BaseModel):
|
|
||||||
id: str
|
|
||||||
user_id: str
|
|
||||||
name: str
|
|
||||||
description: str | None
|
|
||||||
mount_path: str
|
|
||||||
files: dict
|
|
||||||
project_overrides: dict | None
|
|
||||||
is_active: bool
|
|
||||||
created_at: str
|
|
||||||
updated_at: str
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("", summary="List config folders", description="Get all config folders for the current user.")
|
@router.get("", summary="List config folders", description="Get all config folders for the current user.")
|
||||||
async def list_config_folders(
|
async def list_config_folders(
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ from src.services.docker import (
|
|||||||
execute_compose_command,
|
execute_compose_command,
|
||||||
find_free_port,
|
find_free_port,
|
||||||
get_container_id,
|
get_container_id,
|
||||||
|
get_container_logs,
|
||||||
get_container_name,
|
get_container_name,
|
||||||
|
get_container_status,
|
||||||
recreate_tunnel,
|
recreate_tunnel,
|
||||||
render_compose_template,
|
render_compose_template,
|
||||||
start_cloudflared_tunnel,
|
start_cloudflared_tunnel,
|
||||||
@@ -266,7 +268,7 @@ async def create_instance(
|
|||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
detail="config profile not found",
|
detail="config profile not found",
|
||||||
)
|
)
|
||||||
if config_profile.user_id != user_id:
|
if config_profile.user_id != user.id:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
detail="config profile does not belong to user",
|
detail="config profile does not belong to user",
|
||||||
@@ -577,7 +579,7 @@ async def start_instance(
|
|||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
detail="config profile not found",
|
detail="config profile not found",
|
||||||
)
|
)
|
||||||
if selected_profile.user_id != user_id:
|
if selected_profile.user_id != user.id:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
detail="config profile does not belong to user",
|
detail="config profile does not belong to user",
|
||||||
@@ -912,7 +914,7 @@ async def restart_instance(
|
|||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
detail="config profile not found",
|
detail="config profile not found",
|
||||||
)
|
)
|
||||||
if stored_profile.user_id != user_id:
|
if stored_profile.user_id != user.id:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
detail="config profile does not belong to user",
|
detail="config profile does not belong to user",
|
||||||
@@ -1297,7 +1299,7 @@ async def proxy_to_instance(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Verify ownership
|
# Verify ownership
|
||||||
if instance.owner_id != user_id:
|
if instance.owner_id != user.id:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
detail="not authorized to access this instance",
|
detail="not authorized to access this instance",
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
"""Config folder request/response schemas."""
|
"""Config folder request/response schemas."""
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user