diff --git a/autoload.php b/autoload.php index 7704b22..2c5c9f7 100644 --- a/autoload.php +++ b/autoload.php @@ -1,35 +1,37 @@ ClickUp bot # Copyright (C) 2023 Valerio Bozzolan, contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . /** * Autoload all the needed libraries and dependencies */ // load Guzzle require __DIR__ . '/vendor/autoload.php'; // load classes require __DIR__ . '/include/class-ClickUpPhabricatorCache.php'; require __DIR__ . '/include/class-ClickUpAPI.php'; +require __DIR__ . '/include/class-ClickupException.php'; +require __DIR__ . '/include/class-ClickupExceptionTaskNotFound.php'; require __DIR__ . '/include/class-PhabricatorAPI.php'; // load user config require __DIR__ . '/config.php'; // load Arcanist require PHABRICATOR_ARCANIST_PATH; diff --git a/include/class-ClickUpAPI.php b/include/class-ClickUpAPI.php index e11a004..739d483 100644 --- a/include/class-ClickUpAPI.php +++ b/include/class-ClickUpAPI.php @@ -1,308 +1,317 @@ ClickUp bot # Copyright (C) 2023 Valerio Bozzolan, contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . /** * Utility to run HTTP queries against ClickUp */ class ClickUpAPI { /** * https://clickup.com/api/clickupreference/operation/GetSpaces/ * * @return array */ public static function getSpaces() { return self::querySpaces()->spaces ?? []; } /** * https://clickup.com/api/clickupreference/operation/GetSpaces/ * * @return array */ public static function getSpaceFolders( $space_id ) { return self::querySpaceFolders( $space_id )->folders ?? []; } /** * https://clickup.com/api/clickupreference/operation/GetTask/ */ public static function queryTaskData( $task_id ) { // append the Team ID since it does not work otherwise $task_details = self::requestGET( "/task/$task_id", self::query_team() ); // no Task no party if( !$task_details ) { throw new Exception( "missing ClickUp Task $task_id" ); } // save some stuff in the cache ClickUpPhabricatorCache::instance() ->newlock() ->importClickupTaskData( $task_id, $task_details ) ->save(); return $task_details; } /** * https://clickup.com/api/clickupreference/operation/UpdateTask/ */ public static function putTaskData( $task_id, $query = [], $payload = [] ) { $task_details = self::requestPUT( "/task/$task_id", $query, $payload ); ClickUpPhabricatorCache::instance() ->newlock() ->importClickupTaskData( $task_id, $task_details ) ->save(); return $task_details; } /** * Convert a ClickUp status to a Phabricator one * * Note that ClickUp has a "type" and a "status". * * @return boolean */ public static function isStatusClosed( $type, $status = null ) { if( $type === 'closed' ) { return true; } if( $type === 'open' ) { return false; } // "In progress" etc. return false; } /** * Convert a ClickUp status to a Phabricator one * * Note that ClickUp has a "type" and a "status". * * @return string */ public static function taskStatusTypeToPhabricator( $type, $status = null ) { if( $type === 'closed' ) { return 'resolved'; } if( $type === 'open' ) { return $type; } if( $type === 'custom' ) { if( $status === 'in progress' ) { return 'doing'; } } return $status ?? $type; } /** * Register the ClickUp webhook */ public static function registerWebhook( $endpoint = null, $events = null ) { // assume the default endpoint if( !$endpoint) { $endpoint = CLICKUP_WEBHOOK_PUBLIC_ENDPOINT; } // assume all events if( !$events ) { $events = self::default_webhook_events(); } $payload = [ "endpoint" => $endpoint, "events" => $events, ]; $team_id = CLICKUP_TEAM_ID; return self::requestPOST( "/team/$team_id/webhook", [], $payload ); } /** * Add comment to a Task * * https://clickup.com/api/clickupreference/operation/CreateTaskComment/ */ public static function addTaskComment( $task_id, $comment_text ) { $payload = [ 'comment_text' => $comment_text, // 'assignee' => 123, ]; return self::requestPOST( "/task/$task_id/comment", [], $payload ); } /** * https://clickup.com/api/clickupreference/operation/GetWebhooks/ */ public static function queryWebhooks() { $team_id = CLICKUP_TEAM_ID; $response = self::requestGET( "/team/{$team_id}/webhook", [], [] ); return $response->webhooks; } /** * https://clickup.com/api/clickupreference/operation/DeleteWebhook/ */ public static function deleteWebhook( $webhook_id = null ) { if( !$webhook_id ) { $webhook_id = CLICKUP_REGISTERED_WEBHOOK_ID; } $team_id = CLICKUP_TEAM_ID; return self::request( "DELETE", "/team/{$team_id}/webhook/{$webhook_id}", [], [] ); } /** * https://clickup.com/api/clickupreference/operation/UpdateWebhook/ */ public static function updateWebhook( $webhook_id = null, $endpoint = null, $events = [] ) { if( !$webhook_id ) { $webhook_id = CLICKUP_REGISTERED_WEBHOOK_ID; } if( !$endpoint ) { $endpoint = CLICKUP_WEBHOOK_PUBLIC_ENDPOINT; } $payload = [ 'endpoint' => $endpoint, ]; return self::requestPUT( "/webhook/{$webhook_id}", [], $payload ); } /** * https://clickup.com/api/clickupreference/operation/GetSpaces/ * * @return mixed */ private static function querySpaces() { $team_id = CLICKUP_TEAM_ID; return self::requestGET( "/team/$team_id/space" ); } /** * https://clickup.com/api/clickupreference/operation/GetFolders/ */ private static function querySpaceFolders( $space_id ) { $team_id = CLICKUP_TEAM_ID; return self::requestGET( "/space/$space_id/folder" ); } private static function request( $method, $path, $query = [], $payload = [] ) { $API_TOKEN = CLICKUP_API_TOKEN; $url = "https://api.clickup.com/api/v2" . $path; $full_url = $url . '?' . http_build_query( $query ); $curl_opts = [ CURLOPT_HTTPHEADER => [ "Authorization: $API_TOKEN", "Content-Type: application/json" ], CURLOPT_URL => $full_url, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true, ]; // PUT requests if( $payload ) { $payload_raw = json_encode( $payload ); $curl_opts[CURLOPT_POSTFIELDS] = $payload_raw; } $curl = curl_init(); curl_setopt_array( $curl, $curl_opts ); $response = curl_exec( $curl ); $error = curl_error( $curl ); curl_close( $curl ); if( $error ) { throw new Exception( "cURL Error ClickUp #:" . $error ); } $data = json_decode( $response ); if( $data === false ) { throw new Exception( "cannot parse JSON of ClickUp response" ); } // formal error from ClickUp if( isset( $data->err ) ) { - throw new Exception( sprintf( - "ClickUp API error: %s", - json_encode( $data ) - ) ); + + // spawn a nice exact exception if possible + switch( $data->ECODE ) { + case 'ITEM_013': + throw new ClickupExceptionTaskNotFound( $data->err ); + default: + throw new Exception( sprintf( + "ClickUp API error: %s from URL: %s payload: %s", + json_encode( $data ), + $full_url, + json_encode( $payload ) + ) ); + } } return $data; } public static function requestGET( $path, $query = [], $payload = [] ) { return self::request( "GET", $path, $query, $payload ); } public static function requestPUT( $path, $query, $payload = [] ) { return self::request( "PUT", $path, $query, $payload ); } public static function requestPOST( $path, $query, $payload = [] ) { return self::request( "POST", $path, $query, $payload ); } private static function query_team( $query = [] ) { $query[ 'team_id' ] = CLICKUP_TEAM_ID; return $query; } private static function default_webhook_events() { return [ "taskCreated", "taskUpdated", "taskDeleted", "taskPriorityUpdated", "taskStatusUpdated", "taskAssigneeUpdated", "taskDueDateUpdated", "taskTagUpdated", "taskMoved", "taskCommentPosted", "taskCommentUpdated", "taskTimeEstimateUpdated", "taskTimeTrackedUpdated", "listCreated", "listUpdated", "listDeleted", "folderCreated", "folderUpdated", "folderDeleted", "spaceCreated", "spaceUpdated", "spaceDeleted", "goalCreated", "goalUpdated", "goalDeleted", "keyResultCreated", "keyResultUpdated", "keyResultDeleted", ]; } } diff --git a/autoload.php b/include/class-ClickupException.php similarity index 63% copy from autoload.php copy to include/class-ClickupException.php index 7704b22..ec87a55 100644 --- a/autoload.php +++ b/include/class-ClickupException.php @@ -1,35 +1,20 @@ ClickUp bot # Copyright (C) 2023 Valerio Bozzolan, contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -/** - * Autoload all the needed libraries and dependencies - */ - -// load Guzzle -require __DIR__ . '/vendor/autoload.php'; - -// load classes -require __DIR__ . '/include/class-ClickUpPhabricatorCache.php'; -require __DIR__ . '/include/class-ClickUpAPI.php'; -require __DIR__ . '/include/class-PhabricatorAPI.php'; - -// load user config -require __DIR__ . '/config.php'; - -// load Arcanist -require PHABRICATOR_ARCANIST_PATH; +class ClickupException extends Exception { +} diff --git a/autoload.php b/include/class-ClickupExceptionTaskNotFound.php similarity index 63% copy from autoload.php copy to include/class-ClickupExceptionTaskNotFound.php index 7704b22..d5502f6 100644 --- a/autoload.php +++ b/include/class-ClickupExceptionTaskNotFound.php @@ -1,35 +1,20 @@ ClickUp bot # Copyright (C) 2023 Valerio Bozzolan, contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -/** - * Autoload all the needed libraries and dependencies - */ - -// load Guzzle -require __DIR__ . '/vendor/autoload.php'; - -// load classes -require __DIR__ . '/include/class-ClickUpPhabricatorCache.php'; -require __DIR__ . '/include/class-ClickUpAPI.php'; -require __DIR__ . '/include/class-PhabricatorAPI.php'; - -// load user config -require __DIR__ . '/config.php'; - -// load Arcanist -require PHABRICATOR_ARCANIST_PATH; +class ClickupExceptionTaskNotFound extends Exception { +}