2025-04-01 20:42:55 +02:00
|
|
|
|
import asyncio
|
|
|
|
|
|
|
2024-02-18 12:10:35 +01:00
|
|
|
|
from fastapi import APIRouter, HTTPException, Path
|
2022-03-20 17:20:56 +01:00
|
|
|
|
from fastapi.params import Depends
|
2023-04-02 11:09:43 +02:00
|
|
|
|
from loguru import logger
|
2022-03-20 17:20:56 +01:00
|
|
|
|
|
2022-10-03 20:22:00 +02:00
|
|
|
|
import app.apps.docs as docs
|
2025-03-19 16:13:50 +01:00
|
|
|
|
import app.apps.service as service
|
2025-04-01 20:42:55 +02:00
|
|
|
|
from app.apps.cache import watch_app_status_changes
|
2025-04-13 21:16:17 +02:00
|
|
|
|
from app.apps.models import AppId, AppStatus, AppStatusQueryResult, AppUninstallInput
|
2021-06-20 10:26:21 +02:00
|
|
|
|
from app.auth.auth_bearer import JWTBearer
|
|
|
|
|
|
|
2021-09-26 21:57:55 +02:00
|
|
|
|
_PREFIX = "apps"
|
|
|
|
|
|
|
|
|
|
|
|
router = APIRouter(prefix=f"/{_PREFIX}", tags=["Apps"])
|
2021-06-20 10:26:21 +02:00
|
|
|
|
|
2022-05-18 18:20:13 +02:00
|
|
|
|
|
2025-04-01 20:42:55 +02:00
|
|
|
|
async def register_app_status_update_handlers():
|
|
|
|
|
|
# This handler watches for messages from the update app cache celery task
|
|
|
|
|
|
# it is also responsible for notifying clients of the change
|
2026-07-03 22:34:44 +02:00
|
|
|
|
asyncio.create_task(watch_app_status_changes())
|
2025-04-01 20:42:55 +02:00
|
|
|
|
|
|
|
|
|
|
|
2021-09-05 08:56:53 +02:00
|
|
|
|
@router.get(
|
|
|
|
|
|
"/status",
|
2021-09-26 21:57:55 +02:00
|
|
|
|
name=f"{_PREFIX}/status",
|
2025-03-19 16:13:50 +01:00
|
|
|
|
summary="Get the status of all available apps.",
|
2021-09-05 08:56:53 +02:00
|
|
|
|
response_description=docs.get_app_status_response_docs,
|
|
|
|
|
|
dependencies=[Depends(JWTBearer())],
|
|
|
|
|
|
)
|
2023-04-02 11:09:43 +02:00
|
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2025-03-19 16:13:50 +01:00
|
|
|
|
async def get_status() -> AppStatusQueryResult:
|
|
|
|
|
|
return await service.get_app_status()
|
2021-06-20 10:26:21 +02:00
|
|
|
|
|
2022-05-18 18:20:13 +02:00
|
|
|
|
|
2025-04-01 20:42:55 +02:00
|
|
|
|
@router.post(
|
|
|
|
|
|
"/update-cache",
|
|
|
|
|
|
name=f"{_PREFIX}/update-cache",
|
|
|
|
|
|
summary="Update the app status cache. Results will be broadcasted to SSE clients.",
|
|
|
|
|
|
dependencies=[Depends(JWTBearer())],
|
|
|
|
|
|
)
|
|
|
|
|
|
@logger.catch(exclude=(HTTPException,))
|
|
|
|
|
|
async def update_cache():
|
|
|
|
|
|
return await service.update_app_state_cache()
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-05-05 13:27:59 +02:00
|
|
|
|
@router.get(
|
|
|
|
|
|
"/status/{id}",
|
|
|
|
|
|
name=f"{_PREFIX}/status",
|
|
|
|
|
|
summary="Get the status of a single app by id.",
|
|
|
|
|
|
dependencies=[Depends(JWTBearer())],
|
2025-03-19 16:13:50 +01:00
|
|
|
|
responses={404: {"description": ("If no or an invalid app id is given.")}},
|
2022-05-05 13:27:59 +02:00
|
|
|
|
)
|
2023-04-02 11:09:43 +02:00
|
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2025-03-19 16:13:50 +01:00
|
|
|
|
async def get_single_status(id) -> AppStatus:
|
|
|
|
|
|
return await service.get_app_status_single(id)
|
2021-06-20 10:26:21 +02:00
|
|
|
|
|
2022-05-18 18:20:13 +02:00
|
|
|
|
|
2024-02-18 12:10:35 +01:00
|
|
|
|
@router.get(
|
|
|
|
|
|
"/status_advanced/{id}",
|
|
|
|
|
|
name=f"{_PREFIX}/status_advanced",
|
|
|
|
|
|
summary="Get the advanced status of a single app by id.",
|
2025-03-19 16:13:50 +01:00
|
|
|
|
description=f"""Some apps might give status information that is computationally
|
2024-02-18 12:10:35 +01:00
|
|
|
|
to expensive to include in the normal status endpoint.
|
|
|
|
|
|
|
2025-03-19 16:13:50 +01:00
|
|
|
|
Available app ids for this endpoint:
|
|
|
|
|
|
- {AppId.ELECTRS.value}
|
|
|
|
|
|
|
|
|
|
|
|
> ℹ️ _This endpoint is not implemented on all platforms_
|
2024-02-18 12:10:35 +01:00
|
|
|
|
""",
|
|
|
|
|
|
dependencies=[Depends(JWTBearer())],
|
2025-03-19 16:13:50 +01:00
|
|
|
|
responses={404: {"description": ("If no or an invalid app id is given.")}},
|
2024-02-18 12:10:35 +01:00
|
|
|
|
)
|
|
|
|
|
|
@logger.catch(exclude=(HTTPException,))
|
|
|
|
|
|
async def get_single_status_advanced(id: str = Path(..., required=True)):
|
2025-03-19 16:13:50 +01:00
|
|
|
|
return await service.get_app_status_advanced(id)
|
2022-05-05 13:27:59 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post(
|
2025-04-13 21:16:17 +02:00
|
|
|
|
"/install/{app_id}",
|
2022-05-05 13:27:59 +02:00
|
|
|
|
name=f"{_PREFIX}/install",
|
2025-04-13 21:16:17 +02:00
|
|
|
|
summary="Install an app",
|
|
|
|
|
|
description="""Attempts to install an app. The installation process results are
|
|
|
|
|
|
not returned on this endpoint. Instead, the results are communicated via the SSE
|
|
|
|
|
|
channels. This call only verifies if the app is available for installation on the
|
|
|
|
|
|
given platform.""",
|
|
|
|
|
|
responses={
|
|
|
|
|
|
400: {
|
|
|
|
|
|
"description": "If the app is already installed "
|
|
|
|
|
|
"or not available for the platform."
|
|
|
|
|
|
},
|
|
|
|
|
|
404: {"description": "If no or an invalid app id is given."},
|
|
|
|
|
|
423: {"description": "If an app install task is already running."},
|
|
|
|
|
|
},
|
2022-05-05 13:27:59 +02:00
|
|
|
|
dependencies=[Depends(JWTBearer())],
|
|
|
|
|
|
)
|
2023-04-02 11:09:43 +02:00
|
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2025-04-13 21:16:17 +02:00
|
|
|
|
async def install_app(app_id: AppId):
|
|
|
|
|
|
await service.install_app(app_id)
|
2022-05-18 18:20:13 +02:00
|
|
|
|
|
2022-05-05 13:27:59 +02:00
|
|
|
|
|
|
|
|
|
|
@router.post(
|
2025-04-13 21:16:17 +02:00
|
|
|
|
"/uninstall",
|
|
|
|
|
|
name=f"{_PREFIX}/uninstall",
|
2022-05-05 13:27:59 +02:00
|
|
|
|
summary="Uninstall app",
|
2025-04-13 21:16:17 +02:00
|
|
|
|
description="""Attempts to uninstall an app. The uninstallation process results are
|
|
|
|
|
|
not returned on this endpoint. Instead, the results are communicated via the SSE
|
|
|
|
|
|
channels. This call only verifies if the app is available for uninstallation on the
|
|
|
|
|
|
given platform.""",
|
|
|
|
|
|
responses={
|
|
|
|
|
|
400: {"description": ("If the app is already installed.")},
|
|
|
|
|
|
404: {"description": ("If no or an invalid app id is given.")},
|
|
|
|
|
|
},
|
2022-05-05 13:27:59 +02:00
|
|
|
|
dependencies=[Depends(JWTBearer())],
|
|
|
|
|
|
)
|
2023-04-02 11:09:43 +02:00
|
|
|
|
@logger.catch(exclude=(HTTPException,))
|
2025-04-13 21:16:17 +02:00
|
|
|
|
async def uninstall_app(input: AppUninstallInput):
|
|
|
|
|
|
await service.uninstall_app(input)
|