117 lines
4.0 KiB
Python
117 lines
4.0 KiB
Python
"""API routes for board-to-repository linking operations."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlmodel import select
|
|
from sqlalchemy.exc import IntegrityError
|
|
|
|
from app.api.deps import (
|
|
get_board_for_actor_read,
|
|
get_board_for_actor_write,
|
|
)
|
|
from app.core.logging import get_logger
|
|
from app.core.time import utcnow
|
|
from app.db import crud
|
|
from app.db.session import get_session
|
|
from app.models.forgejo_repositories import ForgejoRepository
|
|
from app.models.board_repository_links import BoardRepositoryLink
|
|
from app.schemas.board_repository_links import (
|
|
BoardRepositoryLinkCreate,
|
|
BoardRepositoryLinkDeleteResponse,
|
|
BoardRepositoryLinkRead,
|
|
BoardRepositoryLinkResponse,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from sqlalchemy.ext.asyncio.session import AsyncSession
|
|
|
|
|
|
router = APIRouter(prefix="/boards/{board_id}/forgejo/repositories", tags=["board-repositories"])
|
|
logger = get_logger(__name__)
|
|
SESSION_DEP = Depends(get_session)
|
|
BOARD_READ_DEP = Depends(get_board_for_actor_read)
|
|
BOARD_WRITE_DEP = Depends(get_board_for_actor_write)
|
|
|
|
|
|
@router.get("", response_model=list[BoardRepositoryLinkRead])
|
|
async def list_board_repositories(
|
|
board_id: UUID,
|
|
session: AsyncSession = SESSION_DEP,
|
|
board: BoardRepositoryLink = BOARD_READ_DEP,
|
|
) -> list[BoardRepositoryLinkRead]:
|
|
"""List repositories linked to a board."""
|
|
statement = select(BoardRepositoryLink).where(
|
|
BoardRepositoryLink.board_id == board_id
|
|
)
|
|
links = (await session.exec(statement)).all()
|
|
return [BoardRepositoryLinkRead.model_validate(link) for link in links]
|
|
|
|
|
|
@router.post("", response_model=BoardRepositoryLinkResponse)
|
|
async def link_repository_to_board(
|
|
board_id: UUID,
|
|
payload: BoardRepositoryLinkCreate,
|
|
session: AsyncSession = SESSION_DEP,
|
|
board: BoardRepositoryLink = BOARD_WRITE_DEP,
|
|
) -> BoardRepositoryLinkResponse:
|
|
"""Link a Forgejo repository to a board."""
|
|
# Verify repository belongs to same organization as board
|
|
repository = await crud.get_by_id(session, ForgejoRepository, payload.repository_id)
|
|
if repository is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Repository not found or access denied",
|
|
)
|
|
if repository.organization_id != board.organization_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Repository must belong to the same organization as the board",
|
|
)
|
|
|
|
# Create the link
|
|
link = BoardRepositoryLink(
|
|
board_id=board_id,
|
|
repository_id=payload.repository_id,
|
|
organization_id=board.organization_id,
|
|
)
|
|
try:
|
|
await crud.create(session, BoardRepositoryLink, **link.model_dump())
|
|
await session.flush()
|
|
link_read = BoardRepositoryLinkRead.model_validate(link)
|
|
return BoardRepositoryLinkResponse(link=link_read)
|
|
except IntegrityError:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_409_CONFLICT,
|
|
detail="Repository is already linked to this board",
|
|
)
|
|
|
|
|
|
@router.delete("/{repository_id}", response_model=BoardRepositoryLinkDeleteResponse)
|
|
async def unlink_repository_from_board(
|
|
board_id: UUID,
|
|
repository_id: UUID,
|
|
session: AsyncSession = SESSION_DEP,
|
|
board: BoardRepositoryLink = BOARD_WRITE_DEP,
|
|
) -> BoardRepositoryLinkDeleteResponse:
|
|
"""Remove a repository link from a board."""
|
|
statement = select(BoardRepositoryLink).where(
|
|
BoardRepositoryLink.board_id == board_id,
|
|
BoardRepositoryLink.repository_id == repository_id,
|
|
)
|
|
link = (await session.exec(statement)).first()
|
|
if link is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Repository link not found",
|
|
)
|
|
await session.delete(link)
|
|
await session.commit()
|
|
return BoardRepositoryLinkDeleteResponse(
|
|
success=True,
|
|
message="Repository unlinked successfully",
|
|
)
|