feat(FN-004): merge fusion/fn-004

This commit is contained in:
Fusion
2026-05-14 06:47:30 +02:00
parent 4cbd30ff42
commit 3a18a1f170
62 changed files with 2567 additions and 11 deletions
+25
View File
@@ -0,0 +1,25 @@
from fastapi import APIRouter
from app.routers.access_routes import router as access_routes_router
from app.routers.configs import router as configs_router
from app.routers.projects import router as projects_router
from app.routers.repositories import router as repositories_router
from app.routers.secrets import router as secrets_router
from app.routers.tool_definitions import router as tool_definitions_router
from app.routers.tool_instances import router as tool_instances_router
from app.routers.users import router as users_router
from app.routers.workspaces import router as workspaces_router
routers: list[APIRouter] = [
access_routes_router,
configs_router,
projects_router,
repositories_router,
secrets_router,
tool_definitions_router,
tool_instances_router,
users_router,
workspaces_router,
]
__all__ = ["routers"]
+103
View File
@@ -0,0 +1,103 @@
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.dependencies import get_current_active_user
from app.db import get_db_session
from app.models.access_route import AccessRoute
from app.models.project import Project
from app.models.tool_instance import ToolInstance
from app.models.user import User
from app.schemas.access_route import AccessRouteCreate, AccessRouteRead, AccessRouteUpdate
router = APIRouter(tags=["access-routes"])
async def _verify_tool_instance_ownership(
instance_id: UUID, user: User, session: AsyncSession
) -> None:
ti = await session.get(ToolInstance, instance_id)
if not ti:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Tool instance not found")
project = await session.get(Project, ti.project_id)
if not project or project.owner_id != user.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied")
@router.post("/tool-instances/{instance_id}/access-routes", response_model=AccessRouteRead, status_code=status.HTTP_201_CREATED) # noqa: E501
async def create_access_route(
instance_id: UUID,
ar_in: AccessRouteCreate,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> AccessRoute:
await _verify_tool_instance_ownership(instance_id, current_user, session)
ar = AccessRoute(**ar_in.model_dump(), tool_instance_id=instance_id)
session.add(ar)
await session.commit()
await session.refresh(ar)
return ar
@router.get("/tool-instances/{instance_id}/access-routes", response_model=list[AccessRouteRead])
async def list_access_routes(
instance_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> list[AccessRoute]:
await _verify_tool_instance_ownership(instance_id, current_user, session)
result = await session.execute(
select(AccessRoute).where(AccessRoute.tool_instance_id == instance_id)
)
return list(result.scalars().all())
@router.get("/tool-instances/{instance_id}/access-routes/{route_id}", response_model=AccessRouteRead) # noqa: E501
async def get_access_route(
instance_id: UUID,
route_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> AccessRoute:
await _verify_tool_instance_ownership(instance_id, current_user, session)
ar = await session.get(AccessRoute, route_id)
if not ar or ar.tool_instance_id != instance_id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Access route not found")
return ar
@router.put("/tool-instances/{instance_id}/access-routes/{route_id}", response_model=AccessRouteRead) # noqa: E501
async def update_access_route(
instance_id: UUID,
route_id: UUID,
ar_in: AccessRouteUpdate,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> AccessRoute:
await _verify_tool_instance_ownership(instance_id, current_user, session)
ar = await session.get(AccessRoute, route_id)
if not ar or ar.tool_instance_id != instance_id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Access route not found")
update_data = ar_in.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(ar, field, value)
await session.commit()
await session.refresh(ar)
return ar
@router.delete("/tool-instances/{instance_id}/access-routes/{route_id}", status_code=status.HTTP_204_NO_CONTENT) # noqa: E501
async def delete_access_route(
instance_id: UUID,
route_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> None:
await _verify_tool_instance_ownership(instance_id, current_user, session)
ar = await session.get(AccessRoute, route_id)
if not ar or ar.tool_instance_id != instance_id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Access route not found")
await session.delete(ar)
await session.commit()
+122
View File
@@ -0,0 +1,122 @@
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.dependencies import get_current_active_user
from app.db import get_db_session
from app.models.config import Config
from app.models.project import Project
from app.models.tool_instance import ToolInstance
from app.models.user import User
from app.schemas.config import ConfigCreate, ConfigRead, ConfigUpdate
router = APIRouter(tags=["configs"])
async def _verify_config_ownership(
config_obj: Config, user: User, session: AsyncSession
) -> None:
if config_obj.scope_type == "user":
if config_obj.scope_id != user.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied")
elif config_obj.scope_type == "project":
project = await session.get(Project, config_obj.scope_id)
if not project or project.owner_id != user.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied")
elif config_obj.scope_type == "tool_instance":
ti = await session.get(ToolInstance, config_obj.scope_id)
if not ti:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied")
project = await session.get(Project, ti.project_id)
if not project or project.owner_id != user.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied")
elif config_obj.scope_type == "global":
pass
else:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid scope_type")
@router.post("/configs", response_model=ConfigRead, status_code=status.HTTP_201_CREATED)
async def create_config(
config_in: ConfigCreate,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> Config:
cfg = Config(**config_in.model_dump())
await _verify_config_ownership(cfg, current_user, session)
session.add(cfg)
await session.commit()
await session.refresh(cfg)
return cfg
@router.get("/configs", response_model=list[ConfigRead])
async def list_configs(
scope_type: str | None = None,
scope_id: UUID | None = None,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> list[Config]:
stmt = select(Config)
if scope_type:
stmt = stmt.where(Config.scope_type == scope_type)
if scope_id:
stmt = stmt.where(Config.scope_id == scope_id)
result = await session.execute(stmt)
configs = list(result.scalars().all())
allowed = []
for cfg in configs:
try:
await _verify_config_ownership(cfg, current_user, session)
allowed.append(cfg)
except HTTPException:
pass
return allowed
@router.get("/configs/{config_id}", response_model=ConfigRead)
async def get_config(
config_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> Config:
cfg = await session.get(Config, config_id)
if not cfg:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Config not found")
await _verify_config_ownership(cfg, current_user, session)
return cfg
@router.put("/configs/{config_id}", response_model=ConfigRead)
async def update_config(
config_id: UUID,
config_in: ConfigUpdate,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> Config:
cfg = await session.get(Config, config_id)
if not cfg:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Config not found")
await _verify_config_ownership(cfg, current_user, session)
update_data = config_in.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(cfg, field, value)
await session.commit()
await session.refresh(cfg)
return cfg
@router.delete("/configs/{config_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_config(
config_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> None:
cfg = await session.get(Config, config_id)
if not cfg:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Config not found")
await _verify_config_ownership(cfg, current_user, session)
await session.delete(cfg)
await session.commit()
+80
View File
@@ -0,0 +1,80 @@
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.dependencies import get_current_active_user
from app.db import get_db_session
from app.models.project import Project
from app.models.user import User
from app.schemas.project import ProjectCreate, ProjectRead, ProjectUpdate
router = APIRouter(tags=["projects"])
@router.post("/projects", response_model=ProjectRead, status_code=status.HTTP_201_CREATED)
async def create_project(
project_in: ProjectCreate,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> Project:
project = Project(**project_in.model_dump(), owner_id=current_user.id)
session.add(project)
await session.commit()
await session.refresh(project)
return project
@router.get("/projects", response_model=list[ProjectRead])
async def list_projects(
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> list[Project]:
result = await session.execute(
select(Project).where(Project.owner_id == current_user.id)
)
return list(result.scalars().all())
@router.get("/projects/{project_id}", response_model=ProjectRead)
async def get_project(
project_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> Project:
project = await session.get(Project, project_id)
if not project or project.owner_id != current_user.id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
return project
@router.put("/projects/{project_id}", response_model=ProjectRead)
async def update_project(
project_id: UUID,
project_in: ProjectUpdate,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> Project:
project = await session.get(Project, project_id)
if not project or project.owner_id != current_user.id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
update_data = project_in.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(project, field, value)
await session.commit()
await session.refresh(project)
return project
@router.delete("/projects/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_project(
project_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> None:
project = await session.get(Project, project_id)
if not project or project.owner_id != current_user.id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
await session.delete(project)
await session.commit()
+100
View File
@@ -0,0 +1,100 @@
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.dependencies import get_current_active_user
from app.db import get_db_session
from app.models.project import Project
from app.models.repository import Repository
from app.models.user import User
from app.schemas.repository import RepositoryCreate, RepositoryRead, RepositoryUpdate
router = APIRouter(tags=["repositories"])
async def _get_project_for_user(
project_id: UUID, user: User, session: AsyncSession
) -> Project:
project = await session.get(Project, project_id)
if not project or project.owner_id != user.id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
return project
@router.post("/projects/{project_id}/repositories", response_model=RepositoryRead, status_code=status.HTTP_201_CREATED) # noqa: E501
async def create_repository(
project_id: UUID,
repo_in: RepositoryCreate,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> Repository:
await _get_project_for_user(project_id, current_user, session)
repo = Repository(**repo_in.model_dump(), project_id=project_id)
session.add(repo)
await session.commit()
await session.refresh(repo)
return repo
@router.get("/projects/{project_id}/repositories", response_model=list[RepositoryRead])
async def list_repositories(
project_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> list[Repository]:
await _get_project_for_user(project_id, current_user, session)
result = await session.execute(
select(Repository).where(Repository.project_id == project_id)
)
return list(result.scalars().all())
@router.get("/projects/{project_id}/repositories/{repo_id}", response_model=RepositoryRead)
async def get_repository(
project_id: UUID,
repo_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> Repository:
await _get_project_for_user(project_id, current_user, session)
repo = await session.get(Repository, repo_id)
if not repo or repo.project_id != project_id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Repository not found")
return repo
@router.put("/projects/{project_id}/repositories/{repo_id}", response_model=RepositoryRead)
async def update_repository(
project_id: UUID,
repo_id: UUID,
repo_in: RepositoryUpdate,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> Repository:
await _get_project_for_user(project_id, current_user, session)
repo = await session.get(Repository, repo_id)
if not repo or repo.project_id != project_id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Repository not found")
update_data = repo_in.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(repo, field, value)
await session.commit()
await session.refresh(repo)
return repo
@router.delete("/projects/{project_id}/repositories/{repo_id}", status_code=status.HTTP_204_NO_CONTENT) # noqa: E501
async def delete_repository(
project_id: UUID,
repo_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> None:
await _get_project_for_user(project_id, current_user, session)
repo = await session.get(Repository, repo_id)
if not repo or repo.project_id != project_id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Repository not found")
await session.delete(repo)
await session.commit()
+153
View File
@@ -0,0 +1,153 @@
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.dependencies import get_current_active_user
from app.db import get_db_session
from app.encryption import decrypt_value, encrypt_value
from app.models.project import Project
from app.models.secret import Secret
from app.models.tool_instance import ToolInstance
from app.models.user import User
from app.schemas.secret import SecretCreate, SecretRead, SecretUpdate
router = APIRouter(tags=["secrets"])
async def _verify_secret_ownership(
secret_obj: Secret, user: User, session: AsyncSession
) -> None:
if secret_obj.scope_type == "user":
if secret_obj.scope_id != user.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied")
elif secret_obj.scope_type == "project":
project = await session.get(Project, secret_obj.scope_id)
if not project or project.owner_id != user.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied")
elif secret_obj.scope_type == "tool_instance":
ti = await session.get(ToolInstance, secret_obj.scope_id)
if not ti:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied")
project = await session.get(Project, ti.project_id)
if not project or project.owner_id != user.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied")
elif secret_obj.scope_type == "global":
pass
else:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid scope_type")
@router.post("/secrets", response_model=SecretRead, status_code=status.HTTP_201_CREATED)
async def create_secret(
secret_in: SecretCreate,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> SecretRead:
secret = Secret(
scope_type=secret_in.scope_type,
scope_id=secret_in.scope_id,
key=secret_in.key,
encrypted_value=encrypt_value(secret_in.value),
)
await _verify_secret_ownership(secret, current_user, session)
session.add(secret)
await session.commit()
await session.refresh(secret)
return SecretRead(
id=secret.id,
scope_type=secret.scope_type,
scope_id=secret.scope_id,
key=secret.key,
value=decrypt_value(secret.encrypted_value),
)
@router.get("/secrets", response_model=list[SecretRead])
async def list_secrets(
scope_type: str | None = None,
scope_id: UUID | None = None,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> list[SecretRead]:
stmt = select(Secret)
if scope_type:
stmt = stmt.where(Secret.scope_type == scope_type)
if scope_id:
stmt = stmt.where(Secret.scope_id == scope_id)
result = await session.execute(stmt)
secrets = list(result.scalars().all())
allowed = []
for s in secrets:
try:
await _verify_secret_ownership(s, current_user, session)
allowed.append(SecretRead(
id=s.id,
scope_type=s.scope_type,
scope_id=s.scope_id,
key=s.key,
value=decrypt_value(s.encrypted_value),
))
except HTTPException:
pass
return allowed
@router.get("/secrets/{secret_id}", response_model=SecretRead)
async def get_secret(
secret_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> SecretRead:
s = await session.get(Secret, secret_id)
if not s:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Secret not found")
await _verify_secret_ownership(s, current_user, session)
return SecretRead(
id=s.id,
scope_type=s.scope_type,
scope_id=s.scope_id,
key=s.key,
value=decrypt_value(s.encrypted_value),
)
@router.put("/secrets/{secret_id}", response_model=SecretRead)
async def update_secret(
secret_id: UUID,
secret_in: SecretUpdate,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> SecretRead:
s = await session.get(Secret, secret_id)
if not s:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Secret not found")
await _verify_secret_ownership(s, current_user, session)
if secret_in.key is not None:
s.key = secret_in.key
if secret_in.value is not None:
s.encrypted_value = encrypt_value(secret_in.value)
await session.commit()
await session.refresh(s)
return SecretRead(
id=s.id,
scope_type=s.scope_type,
scope_id=s.scope_id,
key=s.key,
value=decrypt_value(s.encrypted_value),
)
@router.delete("/secrets/{secret_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_secret(
secret_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> None:
s = await session.get(Secret, secret_id)
if not s:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Secret not found")
await _verify_secret_ownership(s, current_user, session)
await session.delete(s)
await session.commit()
+88
View File
@@ -0,0 +1,88 @@
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.dependencies import get_current_active_user
from app.db import get_db_session
from app.models.tool_definition import ToolDefinition
from app.models.user import User
from app.schemas.tool_definition import (
ToolDefinitionCreate,
ToolDefinitionRead,
ToolDefinitionUpdate,
)
router = APIRouter(tags=["tool-definitions"])
@router.post("/tool-definitions", response_model=ToolDefinitionRead, status_code=status.HTTP_201_CREATED) # noqa: E501
async def create_tool_definition(
td_in: ToolDefinitionCreate,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> ToolDefinition:
td = ToolDefinition(**td_in.model_dump())
session.add(td)
await session.commit()
await session.refresh(td)
return td
@router.get("/tool-definitions", response_model=list[ToolDefinitionRead])
async def list_tool_definitions(
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> list[ToolDefinition]:
result = await session.execute(select(ToolDefinition))
return list(result.scalars().all())
@router.get("/tool-definitions/{tool_def_id}", response_model=ToolDefinitionRead)
async def get_tool_definition(
tool_def_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> ToolDefinition:
td = await session.get(ToolDefinition, tool_def_id)
if not td:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Tool definition not found"
)
return td
@router.put("/tool-definitions/{tool_def_id}", response_model=ToolDefinitionRead)
async def update_tool_definition(
tool_def_id: UUID,
td_in: ToolDefinitionUpdate,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> ToolDefinition:
td = await session.get(ToolDefinition, tool_def_id)
if not td:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Tool definition not found"
)
update_data = td_in.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(td, field, value)
await session.commit()
await session.refresh(td)
return td
@router.delete("/tool-definitions/{tool_def_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_tool_definition(
tool_def_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> None:
td = await session.get(ToolDefinition, tool_def_id)
if not td:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Tool definition not found"
)
await session.delete(td)
await session.commit()
+100
View File
@@ -0,0 +1,100 @@
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.dependencies import get_current_active_user
from app.db import get_db_session
from app.models.project import Project
from app.models.tool_instance import ToolInstance
from app.models.user import User
from app.schemas.tool_instance import ToolInstanceCreate, ToolInstanceRead, ToolInstanceUpdate
router = APIRouter(tags=["tool-instances"])
async def _get_project_for_user(
project_id: UUID, user: User, session: AsyncSession
) -> Project:
project = await session.get(Project, project_id)
if not project or project.owner_id != user.id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
return project
@router.post("/projects/{project_id}/tool-instances", response_model=ToolInstanceRead, status_code=status.HTTP_201_CREATED) # noqa: E501
async def create_tool_instance(
project_id: UUID,
ti_in: ToolInstanceCreate,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> ToolInstance:
await _get_project_for_user(project_id, current_user, session)
ti = ToolInstance(**ti_in.model_dump(), project_id=project_id)
session.add(ti)
await session.commit()
await session.refresh(ti)
return ti
@router.get("/projects/{project_id}/tool-instances", response_model=list[ToolInstanceRead])
async def list_tool_instances(
project_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> list[ToolInstance]:
await _get_project_for_user(project_id, current_user, session)
result = await session.execute(
select(ToolInstance).where(ToolInstance.project_id == project_id)
)
return list(result.scalars().all())
@router.get("/projects/{project_id}/tool-instances/{instance_id}", response_model=ToolInstanceRead)
async def get_tool_instance(
project_id: UUID,
instance_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> ToolInstance:
await _get_project_for_user(project_id, current_user, session)
ti = await session.get(ToolInstance, instance_id)
if not ti or ti.project_id != project_id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Tool instance not found")
return ti
@router.put("/projects/{project_id}/tool-instances/{instance_id}", response_model=ToolInstanceRead)
async def update_tool_instance(
project_id: UUID,
instance_id: UUID,
ti_in: ToolInstanceUpdate,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> ToolInstance:
await _get_project_for_user(project_id, current_user, session)
ti = await session.get(ToolInstance, instance_id)
if not ti or ti.project_id != project_id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Tool instance not found")
update_data = ti_in.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(ti, field, value)
await session.commit()
await session.refresh(ti)
return ti
@router.delete("/projects/{project_id}/tool-instances/{instance_id}", status_code=status.HTTP_204_NO_CONTENT) # noqa: E501
async def delete_tool_instance(
project_id: UUID,
instance_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> None:
await _get_project_for_user(project_id, current_user, session)
ti = await session.get(ToolInstance, instance_id)
if not ti or ti.project_id != project_id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Tool instance not found")
await session.delete(ti)
await session.commit()
+17
View File
@@ -0,0 +1,17 @@
from fastapi import APIRouter, Depends
from app.auth.dependencies import get_current_active_user
from app.models.user import User
from app.schemas.user import UserRead
router = APIRouter(tags=["users"])
@router.get("/users/me", response_model=UserRead)
async def read_current_user(current_user: User = Depends(get_current_active_user)) -> User:
return current_user
@router.get("/users", response_model=list[UserRead])
async def list_users(current_user: User = Depends(get_current_active_user)) -> list[User]:
return [current_user]
+100
View File
@@ -0,0 +1,100 @@
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.dependencies import get_current_active_user
from app.db import get_db_session
from app.models.project import Project
from app.models.user import User
from app.models.workspace import Workspace
from app.schemas.workspace import WorkspaceCreate, WorkspaceRead, WorkspaceUpdate
router = APIRouter(tags=["workspaces"])
async def _get_project_for_user(
project_id: UUID, user: User, session: AsyncSession
) -> Project:
project = await session.get(Project, project_id)
if not project or project.owner_id != user.id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
return project
@router.post("/projects/{project_id}/workspaces", response_model=WorkspaceRead, status_code=status.HTTP_201_CREATED) # noqa: E501
async def create_workspace(
project_id: UUID,
ws_in: WorkspaceCreate,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> Workspace:
await _get_project_for_user(project_id, current_user, session)
ws = Workspace(**ws_in.model_dump(), project_id=project_id)
session.add(ws)
await session.commit()
await session.refresh(ws)
return ws
@router.get("/projects/{project_id}/workspaces", response_model=list[WorkspaceRead])
async def list_workspaces(
project_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> list[Workspace]:
await _get_project_for_user(project_id, current_user, session)
result = await session.execute(
select(Workspace).where(Workspace.project_id == project_id)
)
return list(result.scalars().all())
@router.get("/projects/{project_id}/workspaces/{ws_id}", response_model=WorkspaceRead)
async def get_workspace(
project_id: UUID,
ws_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> Workspace:
await _get_project_for_user(project_id, current_user, session)
ws = await session.get(Workspace, ws_id)
if not ws or ws.project_id != project_id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Workspace not found")
return ws
@router.put("/projects/{project_id}/workspaces/{ws_id}", response_model=WorkspaceRead)
async def update_workspace(
project_id: UUID,
ws_id: UUID,
ws_in: WorkspaceUpdate,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> Workspace:
await _get_project_for_user(project_id, current_user, session)
ws = await session.get(Workspace, ws_id)
if not ws or ws.project_id != project_id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Workspace not found")
update_data = ws_in.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(ws, field, value)
await session.commit()
await session.refresh(ws)
return ws
@router.delete("/projects/{project_id}/workspaces/{ws_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_workspace(
project_id: UUID,
ws_id: UUID,
current_user: User = Depends(get_current_active_user),
session: AsyncSession = Depends(get_db_session),
) -> None:
await _get_project_for_user(project_id, current_user, session)
ws = await session.get(Workspace, ws_id)
if not ws or ws.project_id != project_id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Workspace not found")
await session.delete(ws)
await session.commit()