refactor: extract shared auth dependencies (Task 3.1)

- Add get_owned_project() to auth/dependencies.py
- Remove duplicated _get_user() and _get_owned_project() from all routers
- Update tool_instances, git_repositories, projects, ssh_keys, users,
  user_config, tool_types routers to use FastAPI dependency injection
- Route handlers now receive User/Project models via Depends() instead
  of calling inline async helpers

Quality gates: Python syntax check (pass), no duplicated helpers (pass)
Refs: repo-restructure Task 3.1
This commit is contained in:
Developer
2026-06-02 19:18:12 +00:00
parent aee3987c24
commit c50d6663d5
8 changed files with 115 additions and 1670 deletions
+39 -91
View File
@@ -10,7 +10,7 @@ from pydantic import BaseModel, ConfigDict
from sqlalchemy import select
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, get_db_session, get_owned_project
from src.config import Settings
from src.models.git_repository import GitRepository
from src.models.project import Project
@@ -40,38 +40,6 @@ router = APIRouter(prefix="/projects", tags=["git-repositories"])
logger = logging.getLogger(__name__)
async def _get_user(session: AsyncSession, user_id: uuid.UUID) -> User:
"""Fetch a user by ID or raise 401 if not found."""
user = await session.get(User, user_id)
if user is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="user not found")
return user
async def _get_owned_project(
project_id: uuid.UUID,
user_id: uuid.UUID,
session: AsyncSession,
) -> Project:
"""Fetch a project and verify ownership.
Args:
project_id: UUID of the project.
user_id: ID of the authenticated user.
session: Database session.
Returns:
The project if found and owned by the user.
Raises:
HTTPException: If project not found or user is not the owner.
"""
project = await session.get(Project, project_id)
if project is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="project not found")
if project.owner_id != user_id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="not project owner")
return project
def _get_repo_path(user_id: uuid.UUID, project_id: uuid.UUID, name: str) -> str:
@@ -214,7 +182,8 @@ class GitRepositoryResponse(BaseModel):
)
async def list_repositories(
project_id: uuid.UUID,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> list[GitRepository]:
"""List all repositories in a project.
@@ -227,8 +196,6 @@ async def list_repositories(
Returns:
List of repositories in the project.
"""
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
result = await session.execute(
select(GitRepository).where(GitRepository.project_id == project_id)
@@ -245,7 +212,8 @@ async def list_repositories(
async def delete_repository(
project_id: uuid.UUID,
repo_id: uuid.UUID,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> Response:
"""Delete a repository.
@@ -259,8 +227,6 @@ async def delete_repository(
Returns:
Empty response with 204 status code.
"""
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
repo = await session.get(GitRepository, repo_id)
if repo is None or repo.project_id != project_id:
@@ -304,7 +270,8 @@ async def parse_repository_url(data: URLParseRequest) -> URLParseResponse:
async def create_repository(
project_id: uuid.UUID,
data: GitRepositoryCreate,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> GitRepository:
"""Create a new git repository.
@@ -318,8 +285,6 @@ async def create_repository(
Returns:
The newly created repository.
"""
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
# Check for duplicate name
existing = await session.execute(
@@ -352,7 +317,7 @@ async def create_repository(
if remote_url:
_preflight_remote_repository(remote_url)
repo_path = _get_repo_path(user_id, project_id, data.name)
repo_path = _get_repo_path(user.id, project_id, data.name)
# Ensure parent directory exists
os.makedirs(os.path.dirname(repo_path), exist_ok=True)
@@ -366,7 +331,7 @@ async def create_repository(
name=data.name,
path=repo_path,
project_id=project_id,
owner_id=user_id,
owner_id=user.id,
is_mirror=False,
remote_url=remote_url,
)
@@ -388,7 +353,8 @@ async def get_repository_history(
branch: str | None = None,
limit: int = 100,
offset: int = 0,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> dict:
"""Get commit history for a repository.
@@ -406,8 +372,6 @@ async def get_repository_history(
Returns:
Dictionary containing commit history data.
"""
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
repo = await session.get(GitRepository, repo_id)
if repo is None or repo.project_id != project_id:
@@ -432,7 +396,8 @@ async def get_repository_commit(
project_id: uuid.UUID,
repo_id: uuid.UUID,
commit_hash: str,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> dict:
"""Get detailed information about a specific commit.
@@ -447,8 +412,6 @@ async def get_repository_commit(
Returns:
Dictionary containing commit details.
"""
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
repo = await session.get(GitRepository, repo_id)
if repo is None or repo.project_id != project_id:
@@ -513,7 +476,8 @@ async def list_repository_files(
repo_id: uuid.UUID,
branch: str = "main",
path: str = "",
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> FileListResponse:
"""List files and directories in a repository path.
@@ -529,8 +493,6 @@ async def list_repository_files(
Returns:
List of files and directories in the specified path.
"""
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
repo = await session.get(GitRepository, repo_id)
if repo is None or repo.project_id != project_id:
@@ -579,7 +541,8 @@ async def get_repository_file_content(
repo_id: uuid.UUID,
branch: str,
path: str,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> FileContentResponse:
"""Get the content of a file.
@@ -595,8 +558,6 @@ async def get_repository_file_content(
Returns:
File content and metadata.
"""
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
repo = await session.get(GitRepository, repo_id)
if repo is None or repo.project_id != project_id:
@@ -632,7 +593,8 @@ async def get_repository_file_content(
async def get_repository_branches(
project_id: uuid.UUID,
repo_id: uuid.UUID,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> BranchesResponse:
"""List all branches in the repository.
@@ -646,8 +608,6 @@ async def get_repository_branches(
Returns:
List of branches and the default branch name.
"""
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
repo = await session.get(GitRepository, repo_id)
if repo is None or repo.project_id != project_id:
@@ -689,7 +649,8 @@ async def update_repository_file(
project_id: uuid.UUID,
repo_id: uuid.UUID,
data: FileUpdateRequest,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> FileUpdateResponse:
"""Update a file and create a commit.
@@ -704,8 +665,6 @@ async def update_repository_file(
Returns:
Commit information for the file update.
"""
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
repo = await session.get(GitRepository, repo_id)
if repo is None or repo.project_id != project_id:
@@ -715,7 +674,6 @@ async def update_repository_file(
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="repository not found on disk")
# Get user info for commit
user = await _get_user(session, user_id)
author_name = user.name or "Unknown"
author_email = user.email or "unknown@example.com"
@@ -761,7 +719,8 @@ class StatusResponse(BaseModel):
async def get_repository_status(
project_id: uuid.UUID,
repo_id: uuid.UUID,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> StatusResponse:
"""Get the working directory status.
@@ -775,8 +734,6 @@ async def get_repository_status(
Returns:
Repository status including branch, modified files, and ahead/behind counts.
"""
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
repo = await session.get(GitRepository, repo_id)
if repo is None or repo.project_id != project_id:
@@ -819,7 +776,8 @@ async def create_repository_branch(
project_id: uuid.UUID,
repo_id: uuid.UUID,
data: BranchCreateRequest,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> dict:
"""Create a new branch.
@@ -834,8 +792,6 @@ async def create_repository_branch(
Returns:
Dictionary with success message and branch name.
"""
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
repo = await session.get(GitRepository, repo_id)
if repo is None or repo.project_id != project_id:
@@ -861,7 +817,8 @@ async def delete_repository_branch(
repo_id: uuid.UUID,
branch_name: str,
force: bool = False,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> dict:
"""Delete a branch.
@@ -877,8 +834,6 @@ async def delete_repository_branch(
Returns:
Dictionary with success message.
"""
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
repo = await session.get(GitRepository, repo_id)
if repo is None or repo.project_id != project_id:
@@ -903,7 +858,8 @@ async def checkout_repository_branch(
project_id: uuid.UUID,
repo_id: uuid.UUID,
data: CheckoutRequest,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> dict:
"""Checkout a branch.
@@ -918,8 +874,6 @@ async def checkout_repository_branch(
Returns:
Dictionary with success message and checked out branch name.
"""
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
repo = await session.get(GitRepository, repo_id)
if repo is None or repo.project_id != project_id:
@@ -955,7 +909,8 @@ async def commit_repository_changes(
project_id: uuid.UUID,
repo_id: uuid.UUID,
data: CommitRequest,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> CommitResponse:
"""Commit changes to the repository.
@@ -970,8 +925,6 @@ async def commit_repository_changes(
Returns:
Commit information including hash and message.
"""
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
repo = await session.get(GitRepository, repo_id)
if repo is None or repo.project_id != project_id:
@@ -981,7 +934,6 @@ async def commit_repository_changes(
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="repository not found on disk")
# Get user info for commit
user = await _get_user(session, user_id)
author_name = user.name or "Unknown"
author_email = user.email or "unknown@example.com"
@@ -1014,7 +966,8 @@ class FetchResponse(BaseModel):
async def fetch_repository(
project_id: uuid.UUID,
repo_id: uuid.UUID,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> FetchResponse:
"""Fetch from remote.
@@ -1028,8 +981,6 @@ async def fetch_repository(
Returns:
Success message.
"""
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
repo = await session.get(GitRepository, repo_id)
if repo is None or repo.project_id != project_id:
@@ -1059,7 +1010,8 @@ async def pull_repository(
project_id: uuid.UUID,
repo_id: uuid.UUID,
branch: str | None = None,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> PullResponse:
"""Pull updates from remote.
@@ -1074,8 +1026,6 @@ async def pull_repository(
Returns:
Success message.
"""
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
repo = await session.get(GitRepository, repo_id)
if repo is None or repo.project_id != project_id:
@@ -1105,7 +1055,8 @@ async def push_repository(
project_id: uuid.UUID,
repo_id: uuid.UUID,
branch: str | None = None,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> PushResponse:
"""Push changes to remote.
@@ -1120,8 +1071,6 @@ async def push_repository(
Returns:
Success message.
"""
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
repo = await session.get(GitRepository, repo_id)
if repo is None or repo.project_id != project_id:
@@ -1158,7 +1107,8 @@ async def merge_repository_branches(
project_id: uuid.UUID,
repo_id: uuid.UUID,
data: MergeRequest,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> MergeResponse:
"""Merge branches.
@@ -1173,8 +1123,6 @@ async def merge_repository_branches(
Returns:
Merge result with commit hash and message.
"""
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
repo = await session.get(GitRepository, repo_id)
if repo is None or repo.project_id != project_id:
+12 -49
View File
@@ -7,7 +7,7 @@ from pydantic import BaseModel, ConfigDict
from sqlalchemy import select
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, get_db_session, get_owned_project
from src.models.git_repository import GitRepository
from src.models.project import Project
from src.models.ssh_key import SSHKey
@@ -16,13 +16,6 @@ from src.models.user import User
router = APIRouter(prefix="/projects", tags=["projects"])
async def _get_user(session: AsyncSession, user_id: uuid.UUID) -> User:
"""Fetch a user by ID or raise 401 if not found."""
user = await session.get(User, user_id)
if user is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="user not found")
return user
class ProjectCreate(BaseModel):
name: str
@@ -57,7 +50,7 @@ class SetDefaultSSHKeyRequest(BaseModel):
)
async def create_project(
data: ProjectCreate,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_db_session),
) -> Project:
"""Create a new project.
@@ -70,7 +63,6 @@ async def create_project(
Returns:
The newly created project.
"""
user = await _get_user(session, user_id)
project = Project(
name=data.name,
description=data.description,
@@ -90,7 +82,7 @@ async def create_project(
description="Retrieve all projects owned by the authenticated user.",
)
async def list_projects(
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_db_session),
) -> list[Project]:
"""List all projects for the authenticated user.
@@ -102,7 +94,6 @@ async def list_projects(
Returns:
List of projects owned by the user.
"""
user = await _get_user(session, user_id)
result = await session.execute(select(Project).where(Project.owner_id == user.id))
return list(result.scalars().all())
@@ -115,7 +106,8 @@ async def list_projects(
)
async def get_project(
project_id: uuid.UUID,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> Project:
"""Get a specific project by ID.
@@ -128,36 +120,10 @@ async def get_project(
Returns:
The requested project.
"""
await _get_user(session, user_id)
return await _get_owned_project(project_id, user_id, session)
async def _get_owned_project(
project_id: uuid.UUID,
user_id: uuid.UUID,
session: AsyncSession,
) -> Project:
"""Fetch a project and verify ownership.
Args:
project_id: UUID of the project.
user_id: ID of the authenticated user.
session: Database session.
Returns:
The project if found and owned by the user.
Raises:
HTTPException: If project not found or user is not the owner.
"""
project = await session.get(Project, project_id)
if project is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="project not found")
if project.owner_id != user_id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="not project owner")
return project
@router.patch(
"/{project_id}",
response_model=ProjectResponse,
@@ -167,7 +133,8 @@ async def _get_owned_project(
async def update_project(
project_id: uuid.UUID,
data: ProjectUpdate,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> Project:
"""Update a project.
@@ -181,8 +148,6 @@ async def update_project(
Returns:
The updated project.
"""
await _get_user(session, user_id)
project = await _get_owned_project(project_id, user_id, session)
if data.name is not None:
project.name = data.name
@@ -202,7 +167,8 @@ async def update_project(
)
async def delete_project(
project_id: uuid.UUID,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> Response:
"""Delete a project and all its repositories.
@@ -215,8 +181,6 @@ async def delete_project(
Returns:
Empty response with 204 status code.
"""
await _get_user(session, user_id)
project = await _get_owned_project(project_id, user_id, session)
# Delete repositories from disk and database
result = await session.execute(select(GitRepository).where(GitRepository.project_id == project_id))
@@ -240,7 +204,8 @@ async def delete_project(
async def set_default_ssh_key(
project_id: uuid.UUID,
data: SetDefaultSSHKeyRequest,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
project: Project = Depends(get_owned_project),
session: AsyncSession = Depends(get_db_session),
) -> Project:
"""Set the default SSH key for a project.
@@ -254,8 +219,6 @@ async def set_default_ssh_key(
Returns:
The updated project.
"""
user = await _get_user(session, user_id)
project = await _get_owned_project(project_id, user_id, session)
ssh_key = await session.get(SSHKey, data.ssh_key_id)
if ssh_key is None or ssh_key.user_id != user.id:
+4 -14
View File
@@ -9,7 +9,7 @@ from pydantic import BaseModel, ConfigDict
from sqlalchemy import select
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, get_db_session
from src.config import Settings
from src.models.ssh_key import SSHKey
from src.models.user import User
@@ -17,13 +17,6 @@ from src.models.user import User
router = APIRouter(prefix="/ssh-keys", tags=["ssh-keys"])
async def _get_user(session: AsyncSession, user_id: uuid.UUID) -> User:
"""Fetch a user by ID or raise 401 if not found."""
user = await session.get(User, user_id)
if user is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="user not found")
return user
def _get_fernet() -> Fernet:
"""Generate a valid Fernet key from the session secret."""
@@ -83,7 +76,7 @@ class SSHKeyResponse(BaseModel):
)
async def create_ssh_key(
data: SSHKeyCreate,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_db_session),
) -> SSHKey:
"""Create a new SSH key pair.
@@ -96,7 +89,6 @@ async def create_ssh_key(
Returns:
The newly created SSH key with public key exposed.
"""
user = await _get_user(session, user_id)
private_key, public_key = generate_ssh_key_pair()
fernet = _get_fernet()
@@ -121,7 +113,7 @@ async def create_ssh_key(
description="List all SSH keys for the authenticated user.",
)
async def list_ssh_keys(
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_db_session),
) -> list[SSHKey]:
"""List all SSH keys for the authenticated user.
@@ -133,7 +125,6 @@ async def list_ssh_keys(
Returns:
List of SSH keys owned by the user.
"""
user = await _get_user(session, user_id)
result = await session.execute(select(SSHKey).where(SSHKey.user_id == user.id))
return list(result.scalars().all())
@@ -146,7 +137,7 @@ async def list_ssh_keys(
)
async def delete_ssh_key(
key_id: uuid.UUID,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_db_session),
) -> None:
"""Delete an SSH key.
@@ -159,7 +150,6 @@ async def delete_ssh_key(
Returns:
None with 204 status code.
"""
user = await _get_user(session, user_id)
ssh_key = await session.get(SSHKey, key_id)
if ssh_key is None or ssh_key.user_id != user.id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="ssh key not found")
File diff suppressed because it is too large Load Diff
+8 -22
View File
@@ -7,20 +7,13 @@ from pydantic import BaseModel, ConfigDict, field_validator, model_validator
from sqlalchemy import select
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, get_db_session
from src.models.tool_type import ToolType
from src.models.user import User
router = APIRouter(prefix="/tool-types", tags=["tool-types"])
async def _get_user(session: AsyncSession, user_id: uuid.UUID) -> User:
"""Fetch a user by ID or raise 401 if not found."""
user = await session.get(User, user_id)
if user is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="user not found")
return user
async def _require_admin(user: User) -> None:
"""Check if user has admin privileges.
@@ -266,7 +259,7 @@ class ToolTypeResponse(BaseModel):
)
async def create_tool_type(
data: ToolTypeCreate,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_db_session),
) -> ToolType:
"""Create a new tool type.
@@ -279,7 +272,6 @@ async def create_tool_type(
Returns:
The newly created tool type.
"""
user = await _get_user(session, user_id)
await _require_admin(user)
# Check for duplicate name
@@ -316,7 +308,7 @@ async def create_tool_type(
description="List all available tool types including built-in and custom ones.",
)
async def list_tool_types(
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_db_session),
) -> list[ToolType]:
"""List all tool types.
@@ -328,7 +320,6 @@ async def list_tool_types(
Returns:
List of all tool types ordered by name.
"""
await _get_user(session, user_id)
result = await session.execute(select(ToolType).order_by(ToolType.name))
return list(result.scalars().all())
@@ -341,7 +332,7 @@ async def list_tool_types(
)
async def get_tool_type(
tool_type_id: uuid.UUID,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_db_session),
) -> ToolType:
"""Get a specific tool type by ID.
@@ -354,7 +345,6 @@ async def get_tool_type(
Returns:
The requested tool type.
"""
await _get_user(session, user_id)
tool_type = await session.get(ToolType, tool_type_id)
if tool_type is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="tool type not found")
@@ -370,7 +360,7 @@ async def get_tool_type(
async def update_tool_type(
tool_type_id: uuid.UUID,
data: ToolTypeUpdate,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_db_session),
) -> ToolType:
"""Update a tool type.
@@ -384,7 +374,6 @@ async def update_tool_type(
Returns:
The updated tool type.
"""
user = await _get_user(session, user_id)
await _require_admin(user)
tool_type = await session.get(ToolType, tool_type_id)
@@ -480,7 +469,7 @@ class ToolTypeValidateRequest(BaseModel):
)
async def validate_tool_type_template(
data: ToolTypeValidateRequest,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_db_session),
) -> dict:
"""Validate a tool type template syntax.
@@ -493,7 +482,6 @@ async def validate_tool_type_template(
Returns:
Validation result with success status and any errors.
"""
await _get_user(session, user_id)
errors = []
@@ -534,7 +522,7 @@ async def validate_tool_type_template(
)
async def validate_tool_type(
tool_type_id: uuid.UUID,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_db_session),
) -> dict:
"""Validate a tool type's template syntax.
@@ -547,7 +535,6 @@ async def validate_tool_type(
Returns:
Validation result with success status and any errors.
"""
await _get_user(session, user_id)
tool_type = await session.get(ToolType, tool_type_id)
if tool_type is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="tool type not found")
@@ -589,7 +576,7 @@ async def validate_tool_type(
)
async def delete_tool_type(
tool_type_id: uuid.UUID,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_db_session),
) -> None:
"""Delete a tool type.
@@ -602,7 +589,6 @@ async def delete_tool_type(
Returns:
None with 204 status code.
"""
user = await _get_user(session, user_id)
await _require_admin(user)
tool_type = await session.get(ToolType, tool_type_id)
+8 -17
View File
@@ -8,20 +8,13 @@ from pydantic import BaseModel, ConfigDict
from sqlalchemy import select
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, get_db_session
from src.models.user import User
from src.models.user_config import UserConfig
router = APIRouter(prefix="/users/me", tags=["user-config"])
async def _get_user(session: AsyncSession, user_id: uuid.UUID) -> User:
"""Fetch a user by ID or raise 401 if not found."""
user = await session.get(User, user_id)
if user is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="user not found")
return user
async def _get_or_create_config(session: AsyncSession, user_id: uuid.UUID) -> UserConfig:
"""Get or create user config record.
@@ -33,10 +26,10 @@ async def _get_or_create_config(session: AsyncSession, user_id: uuid.UUID) -> Us
Returns:
The user's config, creating a new one if it doesn't exist.
"""
result = await session.execute(select(UserConfig).where(UserConfig.user_id == user_id))
result = await session.execute(select(UserConfig).where(UserConfig.user_id == user.id))
config = result.scalar_one_or_none()
if config is None:
config = UserConfig(user_id=user_id, config={})
config = UserConfig(user_id=user.id, config={})
session.add(config)
await session.commit()
await session.refresh(config)
@@ -68,7 +61,7 @@ class UserConfigUpdate(BaseModel):
description="Get the current user's configuration settings.",
)
async def get_user_config(
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_db_session),
) -> UserConfigResponse:
"""Get the current user's configuration.
@@ -80,8 +73,7 @@ async def get_user_config(
Returns:
The user's configuration settings.
"""
_user = await _get_user(session, user_id)
config = await _get_or_create_config(session, user_id)
config = await _get_or_create_config(session, user.id)
return UserConfigResponse.model_validate(config.config)
@@ -93,7 +85,7 @@ async def get_user_config(
)
async def update_user_config(
data: UserConfigUpdate,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_db_session),
) -> UserConfigResponse:
"""Update the current user's configuration.
@@ -106,12 +98,11 @@ async def update_user_config(
Returns:
The updated user configuration.
"""
_user = await _get_user(session, user_id)
config = await _get_or_create_config(session, user_id)
config = await _get_or_create_config(session, user.id)
# Merge updates
update_data = data.model_dump(exclude_unset=True)
logger.info("Updating user config for user %s: %s", user_id, update_data)
logger.info("Updating user config for user %s: %s", user.id, update_data)
# SQLAlchemy JSON doesn't track dict mutations, so we replace the whole dict
config.config = {**config.config, **update_data}
+5 -14
View File
@@ -5,7 +5,7 @@ from fastapi import APIRouter, Depends, HTTPException, UploadFile, status
from pydantic import BaseModel, ConfigDict
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, get_db_session
from src.models.user import User
router = APIRouter(prefix="/users", tags=["users"])
@@ -16,13 +16,6 @@ ALLOWED_CONTENT_TYPES = {"image/png", "image/jpeg", "image/jpg"}
MAX_AVATAR_SIZE = 2 * 1024 * 1024 # 2MB
async def _get_user(session: AsyncSession, user_id: uuid.UUID) -> User:
"""Fetch a user by ID or raise 401 if not found."""
user = await session.get(User, user_id)
if user is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="user not found")
return user
class UserProfileResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
@@ -45,7 +38,7 @@ class UserProfileUpdate(BaseModel):
description="Retrieve the profile of the currently authenticated user.",
)
async def get_profile(
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_db_session),
) -> User:
"""Get the current user's profile.
@@ -57,7 +50,7 @@ async def get_profile(
Returns:
The user's profile information.
"""
return await _get_user(session, user_id)
return user
@router.put(
@@ -68,7 +61,7 @@ async def get_profile(
)
async def update_profile(
data: UserProfileUpdate,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_db_session),
) -> User:
"""Update the current user's profile.
@@ -81,7 +74,6 @@ async def update_profile(
Returns:
The updated user profile.
"""
user = await _get_user(session, user_id)
if data.name is not None:
if len(data.name.strip()) == 0:
@@ -106,7 +98,7 @@ async def update_profile(
)
async def upload_avatar(
file: UploadFile,
user_id: uuid.UUID = Depends(get_current_user_id),
user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_db_session),
) -> User:
"""Upload a profile avatar image.
@@ -119,7 +111,6 @@ async def upload_avatar(
Returns:
The updated user profile with new avatar URL.
"""
user = await _get_user(session, user_id)
if file.content_type not in ALLOWED_CONTENT_TYPES:
raise HTTPException(