Some checks failed
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
226 lines
7.8 KiB
Python
226 lines
7.8 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.dependencies import get_current_user, get_db
|
|
from app.crud.docker_container import DockerContainerRepository
|
|
from app.crud.favorites import FavoriteContainerRepository, FavoriteGroupRepository
|
|
from app.crud.host import HostRepository
|
|
from app.schemas.favorites import (
|
|
FavoriteContainerCreate,
|
|
FavoriteContainerOut,
|
|
FavoriteContainersListResponse,
|
|
FavoriteDockerContainerOut,
|
|
FavoriteGroupCreate,
|
|
FavoriteGroupOut,
|
|
FavoriteGroupsListResponse,
|
|
FavoriteGroupUpdate,
|
|
)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _resolve_user_id(user: dict) -> Optional[int]:
|
|
# For API-key auth, we keep favorites in a shared (user_id NULL) namespace.
|
|
if user.get("type") == "api_key":
|
|
return None
|
|
uid = user.get("user_id")
|
|
if uid is None:
|
|
return None
|
|
try:
|
|
return int(uid)
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
async def _favorite_to_out(db: AsyncSession, favorite, docker_container, host) -> FavoriteContainerOut:
|
|
dc = FavoriteDockerContainerOut(
|
|
host_id=docker_container.host_id,
|
|
host_name=host.name if host else "Unknown",
|
|
host_ip=host.ip_address if host else "Unknown",
|
|
host_docker_status=host.docker_status if host else None,
|
|
container_id=docker_container.container_id,
|
|
name=docker_container.name,
|
|
image=docker_container.image,
|
|
state=docker_container.state,
|
|
status=docker_container.status,
|
|
health=docker_container.health,
|
|
ports=docker_container.ports,
|
|
compose_project=docker_container.compose_project,
|
|
)
|
|
|
|
return FavoriteContainerOut(
|
|
id=favorite.id,
|
|
group_id=favorite.group_id,
|
|
created_at=favorite.created_at,
|
|
docker_container=dc,
|
|
)
|
|
|
|
|
|
@router.get("/groups", response_model=FavoriteGroupsListResponse)
|
|
async def list_favorite_groups(
|
|
user: dict = Depends(get_current_user),
|
|
db_session: AsyncSession = Depends(get_db),
|
|
):
|
|
user_id = _resolve_user_id(user)
|
|
repo = FavoriteGroupRepository(db_session)
|
|
groups = await repo.list_by_user(user_id)
|
|
return FavoriteGroupsListResponse(groups=[FavoriteGroupOut.model_validate(g) for g in groups])
|
|
|
|
|
|
@router.post("/groups", response_model=FavoriteGroupOut, status_code=status.HTTP_201_CREATED)
|
|
async def create_favorite_group(
|
|
payload: FavoriteGroupCreate,
|
|
user: dict = Depends(get_current_user),
|
|
db_session: AsyncSession = Depends(get_db),
|
|
):
|
|
user_id = _resolve_user_id(user)
|
|
repo = FavoriteGroupRepository(db_session)
|
|
|
|
if await repo.exists_name(payload.name, user_id):
|
|
raise HTTPException(status_code=400, detail="Un groupe avec ce nom existe déjà")
|
|
|
|
group = await repo.create(
|
|
user_id=user_id,
|
|
name=payload.name,
|
|
sort_order=payload.sort_order,
|
|
color=payload.color,
|
|
icon_key=payload.icon_key,
|
|
)
|
|
await db_session.commit()
|
|
await db_session.refresh(group)
|
|
return FavoriteGroupOut.model_validate(group)
|
|
|
|
|
|
@router.patch("/groups/{group_id}", response_model=FavoriteGroupOut)
|
|
async def update_favorite_group(
|
|
group_id: int,
|
|
payload: FavoriteGroupUpdate,
|
|
user: dict = Depends(get_current_user),
|
|
db_session: AsyncSession = Depends(get_db),
|
|
):
|
|
user_id = _resolve_user_id(user)
|
|
repo = FavoriteGroupRepository(db_session)
|
|
|
|
group = await repo.get_for_user(group_id, user_id)
|
|
if not group:
|
|
raise HTTPException(status_code=404, detail="Groupe introuvable")
|
|
|
|
if payload.name and payload.name != group.name:
|
|
if await repo.exists_name(payload.name, user_id):
|
|
raise HTTPException(status_code=400, detail="Un groupe avec ce nom existe déjà")
|
|
|
|
group = await repo.update(
|
|
group,
|
|
name=payload.name,
|
|
sort_order=payload.sort_order,
|
|
color=payload.color,
|
|
icon_key=payload.icon_key,
|
|
)
|
|
await db_session.commit()
|
|
await db_session.refresh(group)
|
|
return FavoriteGroupOut.model_validate(group)
|
|
|
|
|
|
@router.delete("/groups/{group_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def delete_favorite_group(
|
|
group_id: int,
|
|
user: dict = Depends(get_current_user),
|
|
db_session: AsyncSession = Depends(get_db),
|
|
):
|
|
user_id = _resolve_user_id(user)
|
|
repo = FavoriteGroupRepository(db_session)
|
|
|
|
group = await repo.get_for_user(group_id, user_id)
|
|
if not group:
|
|
raise HTTPException(status_code=404, detail="Groupe introuvable")
|
|
|
|
await repo.delete(group)
|
|
await db_session.commit()
|
|
|
|
|
|
@router.get("/containers", response_model=FavoriteContainersListResponse)
|
|
async def list_favorite_containers(
|
|
user: dict = Depends(get_current_user),
|
|
db_session: AsyncSession = Depends(get_db),
|
|
):
|
|
user_id = _resolve_user_id(user)
|
|
|
|
fav_repo = FavoriteContainerRepository(db_session)
|
|
container_repo = DockerContainerRepository(db_session)
|
|
host_repo = HostRepository(db_session)
|
|
|
|
favorites = await fav_repo.list_by_user(user_id)
|
|
|
|
out: list[FavoriteContainerOut] = []
|
|
for fav in favorites:
|
|
docker_container = await container_repo.get(fav.docker_container_id)
|
|
if not docker_container:
|
|
# Should be cleaned by FK cascade, but keep the API robust.
|
|
continue
|
|
host = await host_repo.get(docker_container.host_id)
|
|
out.append(await _favorite_to_out(db_session, fav, docker_container, host))
|
|
|
|
return FavoriteContainersListResponse(containers=out)
|
|
|
|
|
|
@router.post("/containers", response_model=FavoriteContainerOut, status_code=status.HTTP_201_CREATED)
|
|
async def add_favorite_container(
|
|
payload: FavoriteContainerCreate,
|
|
user: dict = Depends(get_current_user),
|
|
db_session: AsyncSession = Depends(get_db),
|
|
):
|
|
user_id = _resolve_user_id(user)
|
|
|
|
container_repo = DockerContainerRepository(db_session)
|
|
fav_repo = FavoriteContainerRepository(db_session)
|
|
group_repo = FavoriteGroupRepository(db_session)
|
|
|
|
docker_container = await container_repo.get_by_container_id(payload.host_id, payload.container_id)
|
|
if not docker_container:
|
|
raise HTTPException(status_code=404, detail="Container introuvable")
|
|
|
|
if payload.group_id is not None:
|
|
group = await group_repo.get_for_user(payload.group_id, user_id)
|
|
if not group:
|
|
raise HTTPException(status_code=404, detail="Groupe introuvable")
|
|
|
|
existing = await fav_repo.get_by_docker_container_id(docker_container.id, user_id)
|
|
if existing:
|
|
# If already exists, treat as idempotent and optionally move group.
|
|
if existing.group_id != payload.group_id:
|
|
await fav_repo.update_group(existing, group_id=payload.group_id)
|
|
await db_session.commit()
|
|
await db_session.refresh(existing)
|
|
host_repo = HostRepository(db_session)
|
|
host = await host_repo.get(docker_container.host_id)
|
|
return await _favorite_to_out(db_session, existing, docker_container, host)
|
|
|
|
fav = await fav_repo.create(user_id=user_id, docker_container_db_id=docker_container.id, group_id=payload.group_id)
|
|
await db_session.commit()
|
|
await db_session.refresh(fav)
|
|
|
|
host_repo = HostRepository(db_session)
|
|
host = await host_repo.get(docker_container.host_id)
|
|
return await _favorite_to_out(db_session, fav, docker_container, host)
|
|
|
|
|
|
@router.delete("/containers/{favorite_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def remove_favorite_container(
|
|
favorite_id: int,
|
|
user: dict = Depends(get_current_user),
|
|
db_session: AsyncSession = Depends(get_db),
|
|
):
|
|
user_id = _resolve_user_id(user)
|
|
|
|
fav_repo = FavoriteContainerRepository(db_session)
|
|
fav = await fav_repo.get_for_user(favorite_id, user_id)
|
|
if not fav:
|
|
raise HTTPException(status_code=404, detail="Favori introuvable")
|
|
|
|
await fav_repo.delete(fav)
|
|
await db_session.commit()
|