feat: add missing features from main merge

1. Built-in tool type seeding (apps/api/src/seeds/builtin_tool_types.py):
   - Seeds code-server, jupyter-notebook, and opencode on startup.
   - Adapts to current dev model: uses interface_type (single string)
     instead of interfaces array, and created_by_id=None instead of
     is_builtin flag.
   - Called from main.py startup event.

2. Config profile default management:
   - Adds default_profile_id and default_profiles properties to
     UserConfig model for JSON-backed per-tool-type defaults.
   - Adds GET /config-profiles/defaults, PUT /config-profiles/defaults,
     and GET /config-profiles/defaults/{tool_type_id} endpoints.
   - Validates that all profile IDs in default mappings belong to the
     authenticated user before persisting.

3. Config profile unique constraint:
   - Adds __table_args__ with UniqueConstraint(user_id, name) to
     ConfigProfile model. The constraint already exists in the DB
     from migration 2026_05_24_add_config_profiles.py; this just
     aligns the SQLAlchemy model with the schema.

Quality gates: py_compile passed, ruff passed on all modified files.
This commit is contained in:
2026-06-04 00:00:23 +02:00
parent c6d62f84da
commit 4e076c36d2
6 changed files with 316 additions and 3 deletions
+1
View File
@@ -0,0 +1 @@
"""Database seeding utilities."""
+168
View File
@@ -0,0 +1,168 @@
"""Seed built-in tool types into the database."""
import logging
from sqlalchemy import select, text
from src.database import SessionLocal
from src.models.tool_type import ToolType
logger = logging.getLogger(__name__)
async def _table_exists(session, table_name: str) -> bool:
"""Check if a table exists in the database."""
try:
result = await session.execute(
text(
"""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = :table_name
)
"""
),
{"table_name": table_name},
)
return result.scalar() or False
except Exception:
return False
async def seed_builtin_tool_types():
"""Create or update built-in tool types in the database.
Built-in tool types have no creator (created_by_id=None) and provide
out-of-the-box tools for users without requiring manual tool creation.
"""
async with SessionLocal() as session:
# Check if tool_types table exists before attempting to seed
if not await _table_exists(session, "tool_types"):
logger.warning(
"tool_types table does not exist. Skipping seeding. "
"Migrations may not have run yet."
)
return
builtin_types = [
{
"name": "code-server",
"display_name": "VS Code Server",
"description": "VS Code running in the browser via code-server",
"category": "editor",
"interface_type": "web",
"compose_template": """version: "3.8"
services:
code-server:
image: lscr.io/linuxserver/code-server:latest
container_name: {{TOOL_NAME}}
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/London
volumes:
- {{REPO_PATH}}:/config/workspace
ports:
- "8443:8443"
restart: unless-stopped""",
"default_port": 8443,
"required_variables": ["REPO_PATH", "TOOL_NAME"],
},
{
"name": "jupyter-notebook",
"display_name": "Jupyter Notebook",
"description": "Jupyter Lab for interactive development",
"category": "notebook",
"interface_type": "web",
"default_port": 8888,
"compose_template": """version: "3.8"
services:
jupyter:
image: jupyter/scipy-notebook:latest
container_name: {{TOOL_NAME}}
environment:
- JUPYTER_ENABLE_LAB=yes
volumes:
- {{REPO_PATH}}:/home/jovyan/work
ports:
- "8888:8888"
restart: unless-stopped""",
"required_variables": ["REPO_PATH", "TOOL_NAME"],
},
{
"name": "opencode",
"display_name": "OpenCode",
"description": "AI coding assistant - run opencode in terminal",
"category": "ai-assistant",
"interface_type": "terminal",
"default_port": 3000,
"compose_template": """version: "3.8"
services:
opencode:
image: node:20-slim
container_name: {{TOOL_NAME}}
working_dir: /workspace
environment:
- HOME=/tmp
volumes:
- {{REPO_PATH}}:/workspace
- opencode_home:/tmp
ports:
- "3000:3000"
command: >
sh -c "set -x &&
apt-get update && apt-get install -y git ca-certificates &&
echo 'Installing opencode...' &&
npm install -g opencode-ai 2>&1 || echo 'ERROR: npm install failed' &&
which opencode || echo 'ERROR: opencode not in PATH' &&
npm bin -g &&
ls -la $(npm bin -g) || echo 'ERROR: global bin dir not found' &&
echo 'export PATH=\"$(npm bin -g):\\$PATH\"' >> /root/.bashrc &&
echo 'cd /workspace' >> /root/.bashrc &&
echo 'OpenCode installation complete' &&
cd /workspace &&
exec tail -f /dev/null"
stdin_open: true
tty: true
restart: unless-stopped
volumes:
opencode_home:""",
"required_variables": ["REPO_PATH", "TOOL_NAME"],
},
]
for tool_data in builtin_types:
existing = await session.scalar(
select(ToolType).where(ToolType.name == tool_data["name"])
)
if not existing:
tool_type = ToolType(
name=tool_data["name"],
display_name=tool_data["display_name"],
description=tool_data["description"],
category=tool_data["category"],
interface_type=tool_data["interface_type"],
definition_type="compose",
compose_template=tool_data["compose_template"],
required_variables=tool_data["required_variables"],
default_port=tool_data.get("default_port", 0),
created_by_id=None,
)
session.add(tool_type)
logger.info("Created built-in tool type: %s", tool_data["name"])
else:
# Update existing built-in tool types to reflect code changes
existing.display_name = tool_data["display_name"]
existing.description = tool_data["description"]
existing.category = tool_data["category"]
existing.interface_type = tool_data["interface_type"]
existing.definition_type = "compose"
existing.compose_template = tool_data["compose_template"]
existing.required_variables = tool_data["required_variables"]
existing.default_port = tool_data.get("default_port", 0)
logger.info("Updated built-in tool type: %s", tool_data["name"])
await session.commit()
logger.info("Built-in tool types seeded successfully.")