platform-core/modules/serviceCenter/services/serviceClickup/mainServiceClickup.py
ValueOn AG 4a60086c80
Some checks failed
Deploy Plattform-Core (Int) / test (push) Failing after 15s
Deploy Plattform-Core (Int) / deploy (push) Has been skipped
cp adapted to 2026 poweron
2026-06-09 09:53:31 +02:00

138 lines
5.4 KiB
Python

# Copyright (c) 2026 PowerOn AG
# All rights reserved.
"""ClickUp API service (OAuth or personal token via UserConnection).
Extends the low-level ClickupApiClient from connectors with service-layer
concerns (token resolution via SecurityService, extended query params).
"""
import json
import logging
from typing import Any, Callable, Dict, List, Optional, Union
import aiohttp
from modules.connectors.connectorProviderClickup import ClickupApiClient, clickupAuthorizationHeader
logger = logging.getLogger(__name__)
_CLICKUP_API_BASE = "https://api.clickup.com/api/v2"
def _clickupAuthorizationHeader(token: str) -> str:
"""ClickUp: personal tokens are `pk_...` without Bearer; OAuth uses Bearer."""
return clickupAuthorizationHeader(token)
class ClickupService(ClickupApiClient):
"""ClickUp service — adds token resolution and extended API methods on top of the API client."""
def __init__(self, context, get_service: Callable[[str], Any]):
super().__init__(accessToken="")
self._context = context
self._getService = get_service
def setAccessTokenFromConnection(self, userConnection) -> bool:
"""Load OAuth/personal token from SecurityService for this UserConnection."""
try:
if not userConnection:
logger.error("UserConnection is required to set access token")
return False
if isinstance(userConnection, dict):
connection_id = userConnection.get("id")
else:
connection_id = getattr(userConnection, "id", None)
if not connection_id:
logger.error("UserConnection must have an 'id' field")
return False
security = self._getService("security")
if not security:
logger.error("Security service not available for token access")
return False
token = security.getFreshToken(connection_id)
if not token:
logger.error(f"No token found for connection {connection_id}")
return False
self.accessToken = token.tokenAccess
return True
except Exception as e:
logger.error(f"Error setting ClickUp access token: {e}")
return False
def setAccessToken(self, token: str) -> None:
"""Set token directly (e.g. connector adapter)."""
self.accessToken = token
async def requestRaw(
self,
method: str,
path: str,
*,
params: Optional[Dict[str, Any]] = None,
json_body: Optional[Dict[str, Any]] = None,
) -> Union[Dict[str, Any], List[Any], None]:
"""Escape hatch: call any v2 path under /api/v2 (path without leading /api/v2)."""
return await self._request(method, path, params=params, json_body=json_body)
# --- Extended API methods (beyond base ClickupApiClient) ---
async def getAuthorizedUser(self) -> Dict[str, Any]:
return await self._request("GET", "/user")
async def getTeam(self, team_id: str) -> Dict[str, Any]:
return await self._request("GET", f"/team/{team_id}")
async def getSpace(self, space_id: str) -> Dict[str, Any]:
return await self._request("GET", f"/space/{space_id}")
async def getFolder(self, folder_id: str) -> Dict[str, Any]:
return await self._request("GET", f"/folder/{folder_id}")
async def getList(self, list_id: str) -> Dict[str, Any]:
return await self._request("GET", f"/list/{list_id}")
async def getListFields(self, list_id: str) -> Dict[str, Any]:
return await self._request("GET", f"/list/{list_id}/field")
async def getTasksInList(
self,
list_id: str,
*,
page: int = 0,
include_closed: bool = False,
subtasks: bool = True,
dateCreatedGt: Optional[int] = None,
dateCreatedLt: Optional[int] = None,
dateUpdatedGt: Optional[int] = None,
dateUpdatedLt: Optional[int] = None,
customFields: Optional[List[Dict[str, Any]]] = None,
) -> Dict[str, Any]:
params: Dict[str, Any] = {
"page": page,
"subtasks": str(subtasks).lower(),
"include_closed": str(include_closed).lower(),
}
if dateCreatedGt is not None:
params["date_created_gt"] = dateCreatedGt
if dateCreatedLt is not None:
params["date_created_lt"] = dateCreatedLt
if dateUpdatedGt is not None:
params["date_updated_gt"] = dateUpdatedGt
if dateUpdatedLt is not None:
params["date_updated_lt"] = dateUpdatedLt
if customFields:
params["custom_fields"] = json.dumps(customFields)
return await self._request("GET", f"/list/{list_id}/task", params=params)
async def getTask(self, task_id: str, *, include_subtasks: bool = True) -> Dict[str, Any]:
params = {"include_subtasks": str(include_subtasks).lower()}
return await self._request("GET", f"/task/{task_id}", params=params)
async def createTask(self, list_id: str, body: Dict[str, Any]) -> Dict[str, Any]:
return await self._request("POST", f"/list/{list_id}/task", json_body=body)
async def updateTask(self, task_id: str, body: Dict[str, Any]) -> Dict[str, Any]:
return await self._request("PUT", f"/task/{task_id}", json_body=body)
async def deleteTask(self, task_id: str) -> Dict[str, Any]:
return await self._request("DELETE", f"/task/{task_id}")