From 1bf0c070f88a07daf99d660e45f2ef63a0b59494 Mon Sep 17 00:00:00 2001 From: Benjamin Blake Date: Wed, 19 Feb 2020 22:10:59 -0700 Subject: [PATCH] Huge number of updates, too tired to list them all + Added a bunch of API handler classes that will use the already created models + Created a new Client class that will connect to new API handler classes + Created new collection classes --- .editorconfig | 2 +- composer.json | 2 +- src/Api/AbstractApi.php | 319 ++++++++++++++++++ src/Api/Organizations.php | 46 +++ src/Api/Repositories.php | 115 +++++++ src/Client.php | 147 ++++++++ src/Collections/ApiCollectionInterface.php | 86 +++++ src/Collections/ApiItemCollection.php | 69 ++++ src/Model/Organization.php | 149 ++++++++ src/{models => Model}/PayloadCommit.php | 0 .../PayloadCommitVerification.php | 0 src/{models => Model}/PayloadUser.php | 0 src/{models => Model}/Permission.php | 0 src/{models => Model}/Repository.php | 36 +- src/{models => Model}/ServerVersion.php | 0 src/{models => Model}/StatusState.php | 0 src/{models => Model}/Team.php | 0 src/{models => Model}/TeamPermission.php | 0 src/{models => Model}/TrackedTime.php | 0 src/{models => Model}/User.php | 0 src/TestRun.php | 39 +++ 21 files changed, 1007 insertions(+), 3 deletions(-) create mode 100644 src/Api/AbstractApi.php create mode 100644 src/Api/Organizations.php create mode 100644 src/Api/Repositories.php create mode 100644 src/Client.php create mode 100644 src/Collections/ApiCollectionInterface.php create mode 100644 src/Collections/ApiItemCollection.php create mode 100644 src/Model/Organization.php rename src/{models => Model}/PayloadCommit.php (100%) rename src/{models => Model}/PayloadCommitVerification.php (100%) rename src/{models => Model}/PayloadUser.php (100%) rename src/{models => Model}/Permission.php (100%) rename src/{models => Model}/Repository.php (96%) rename src/{models => Model}/ServerVersion.php (100%) rename src/{models => Model}/StatusState.php (100%) rename src/{models => Model}/Team.php (100%) rename src/{models => Model}/TeamPermission.php (100%) rename src/{models => Model}/TrackedTime.php (100%) rename src/{models => Model}/User.php (100%) create mode 100644 src/TestRun.php diff --git a/.editorconfig b/.editorconfig index 0048ee8..29bc10c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,7 +3,7 @@ root = true [*] charset = utf-8 -indent_size = 2 +indent_size = 4 indent_style = space insert_final_newline = true trim_trailing_whitespace = true diff --git a/composer.json b/composer.json index f63de60..d0b054a 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "ext-curl": "*", "ext-json": "*", "cedx/enum": "^7.4.0", - "guzzlehttp/guzzle": "^6.3.3" + "guzzlehttp/guzzle": "~6.0" }, "require-dev": { "cedx/coveralls": "^10.1.0", diff --git a/src/Api/AbstractApi.php b/src/Api/AbstractApi.php new file mode 100644 index 0000000..7726877 --- /dev/null +++ b/src/Api/AbstractApi.php @@ -0,0 +1,319 @@ + 'application/json' + ]; + + /** + * The default parameters that should be sent + * in all GET requests to this api route + * + * @var array + */ + protected $getDefaultParameters = []; + + /** + * The default headers that should be sent + * in all GET requests to this api route + * + * @var array + */ + protected $getDefaultHeaders = []; + + /** + * The default headers that should be sent + * in all POST requests to this api route + * + * @var array + */ + protected $postDefaultHeaders = [ + 'Content-Type' => 'application/json' + ]; + + /** + * The default headers that should be sent + * in all PUT requests to this api route + * + * @var array + */ + protected $putDefaultHeaders = [ + 'Content-Type' => 'application/json' + ]; + + /** + * The default headers that should be sent + * in all DELETE requests to this api route + * + * @var array + */ + protected $deleteDefaultHeaders = []; + + /** + * @param Client $client + */ + public function __construct(Client $client, $authToken) + { + $this->client = $client; + $this->authToken = $authToken; + + // Set authorization headers and parameters + $this->getDefaultParameters["access_token"] = $authToken; + $this->postDefaultHeaders["Authorization"] = "token $authToken"; + $this->putDefaultHeaders["Authorization"] = "token $authToken"; + $this->deleteDefaultHeaders["Authorization"] = "token $authToken"; + } + + public function getClient() { + return $this->client; + } + + public function getAuthToken() { + return $this->authToken; + } + + /** + * Get the default parameters for a particular type of request + * + * If "all" is passed OR if the $type parameter is left blank + * an array of defaults for ALL request types will be returned + * + * @author Benjamin Blake (sitelease.ca) + * + * @param string $type The type of request ("all","get","post","put",etc.) + * @return array + */ + public function getDefaultParametersForType($type = "all") { + if (!$type || $type == "all") { + return $this->defaultParameters; + } else { + $propertyName = $type."DefaultParameters"; + if (property_exists(__CLASS__, $propertyName) && is_array($this->$propertyName)) { + return array_merge($this->defaultParameters, $this->$propertyName); + } else { + return []; + } + } + } + + /** + * Get the default headers for a particular type of request + * + * If "all" is passed OR if the $type parameter is left blank + * an array of defaults for ALL request types will be returned + * + * @author Benjamin Blake (sitelease.ca) + * + * @param string $type The type of request ("all", "get","post","put",etc.) + * @return array + */ + public function getDefaultHeadersForType($type = "all") { + if (!$type || $type == "all") { + return $this->defaultHeaders; + } else { + $propertyName = $type."DefaultHeaders"; + if (property_exists(__CLASS__, $propertyName) && is_array($this->$propertyName)) { + return array_merge($this->defaultParameters, $this->$propertyName); + } else { + return []; + } + } + } + + /** + * @return $this + * @codeCoverageIgnore + */ + public function configure() + { + return $this; + } + + /** + * Performs a GET query and returns the response as a PSR-7 response object. + * + * @param string $path + * @param array $parameters + * @param array $requestHeaders + * @return ResponseInterface + */ + protected function getAsResponse($path, array $parameters = array(), $requestHeaders = array()) + { + $path = $this->preparePath($path, $parameters); + + return $this->client->getHttpClient()->get($path, $requestHeaders); + } + + /** + * @param string $path + * @param array $parameters + * @param array $requestHeaders + * @return mixed + */ + protected function get($path, array $parameters = array(), $requestHeaders = array(), $debugRequest = false) + { + $client = $this->getClient(); + $guzzleClient = $client->getGuzzleClient(); + $defaultParameters = $this->getDefaultParametersForType("get"); + $defaultHeaders = $this->getDefaultParametersForType("get"); + + // Create request options array + // and populate it with defaults + $requestOptions = []; + $requestOptions['query'] = $defaultParameters; + $requestOptions['headers'] = $defaultHeaders; + if ($debugRequest) { + $requestOptions['debug'] = true; + } + + if ($parameters) { + $requestOptions['query'] = array_merge($defaultParameters, $parameters); + } + + if ($requestHeaders) { + $requestOptions['headers'] = array_merge($defaultHeaders, $requestHeaders); + } + + return $guzzleClient->request('GET', $path, $requestOptions); + } + + /** + * @param string $path + * @param array $body + * @param array $requestHeaders + * @return mixed + */ + protected function post($path, $body, $requestHeaders = array(), $debugRequest = false) + { + $client = $this->getClient(); + $guzzleClient = $client->getGuzzleClient(); + $defaultHeaders = $this->getDefaultHeadersForType("post"); + + // Create request options array + // and populate it with defaults + $requestOptions = []; + $requestOptions['headers'] = $defaultHeaders; + $requestOptions['body'] = "{}"; + if ($debugRequest) { + $requestOptions['debug'] = true; + } + + if ($body) { + if (is_object($body)) { + $requestOptions['body'] = json_encode($body); + } + + if (is_string($body)) { + $requestOptions['body'] = $body; + } + } + + if ($requestHeaders) { + $requestOptions['headers'] = array_merge($defaultHeaders, $requestHeaders); + } + + return $guzzleClient->request('POST', $path, $requestOptions); + } + + /** + * @param string $path + * @param array $body + * @param array $requestHeaders + * @return mixed + */ + protected function put($path, $body, $requestHeaders = array(), $debugRequest = false) + { + $client = $this->getClient(); + $guzzleClient = $client->getGuzzleClient(); + $defaultHeaders = $this->getDefaultHeadersForType("put"); + + // Create request options array + // and populate it with defaults + $requestOptions = []; + $requestOptions['headers'] = $defaultHeaders; + $requestOptions['body'] = "{}"; + if ($debugRequest) { + $requestOptions['debug'] = true; + } + + if ($body) { + if (is_object($body)) { + $requestOptions['body'] = json_encode($body); + } + + if (is_string($body)) { + $requestOptions['body'] = $body; + } + } + + if ($requestHeaders) { + $requestOptions['headers'] = array_merge($defaultHeaders, $requestHeaders); + } + + return $guzzleClient->request('PUT', $path, $requestOptions); + } + + /** + * @param string $path + * @param array $requestHeaders + * @return mixed + */ + protected function delete($path, $requestHeaders = array(), $debugRequest = false) + { + $client = $this->getClient(); + $guzzleClient = $client->getGuzzleClient(); + $defaultHeaders = $this->getDefaultHeadersForType("delete"); + + // Create request options array + // and populate it with defaults + $requestOptions = []; + $requestOptions['headers'] = $defaultHeaders; + if ($debugRequest) { + $requestOptions['debug'] = true; + } + + if ($requestHeaders) { + $requestOptions['headers'] = array_merge($defaultHeaders, $requestHeaders); + } + + return $guzzleClient->request('DELETE', $path, $requestOptions); + } +} diff --git a/src/Api/Organizations.php b/src/Api/Organizations.php new file mode 100644 index 0000000..4b979a3 --- /dev/null +++ b/src/Api/Organizations.php @@ -0,0 +1,46 @@ +organizations()->getByUsername($orgUsername); + * ``` + * + * @param string $username + * @author Benjamin Blake (sitelease.ca) + * + * @return Organization|Response + */ + public function getByUsername(string $username) + { + $client = $this->getClient(); + try { + $response = $this->get("orgs/$username"); + $statusCode = $response->getStatusCode(); + $body = $response->getBody(); + if ($statusCode == 200) { + return Organization::fromJson(json_decode($body)); + } else { + return $response; + } + } catch (ServerException $serverError) { + return $response; + } + } + +} diff --git a/src/Api/Repositories.php b/src/Api/Repositories.php new file mode 100644 index 0000000..9244c33 --- /dev/null +++ b/src/Api/Repositories.php @@ -0,0 +1,115 @@ +getMaxPageCount(); + // Loop over pages until the $maxPageCount is reached + for ($pageNum=1; $pageNum < $maxPageCount; $pageNum++) { + $searchItemsCollection = $this->search("", $pageNum); + if ($searchItemsCollection && $searchItemsCollection->count() > 0) { + $searchItemsArray = $searchItemsCollection->toArray(); + $allItems = array_merge($allItems, $searchItemsArray); + } else { + break; + } + } + return new ApiItemCollection($allItems); + } + + /** + * Search all repositories and return a list of them + * + * Example: + * ``` + * $giteaClient->repositories()->search($keyword, $page); + * ``` + * + * @param string $keyword Keyword to search by + * @param integer $page The page of items to return + * @param integer $limit Maximum number of items per page + * @param array $extraOptions An array of extra options to pass the API reoute + * @return void + * + * @return ApiItemCollection + */ + public function search(string $keyword = "", int $page = 1, int $limit = null, array $extraOptions = array()) + { + $client = $this->getClient(); + $limit = $limit ?? $this->getItemsPerPage(); + + $repositoryCollection = new ApiItemCollection(); + try { + $response = $this->get("repos/search",[ + "page" => $page, + "limit" => $limit + ]); + $statusCode = $response->getStatusCode(); + $body = (string) $response->getBody(); + if ($statusCode == 200) { + $jsonObj = json_decode($body, true); + $jsonOk = $jsonObj["ok"]; + $jsonItemList = $jsonObj["data"]; + + if ($jsonOk && count($jsonItemList) > 0) { + foreach ($jsonItemList as $jsonItem) { + $encodedItem = json_encode($jsonItem); + $itemObject = Repository::fromJson(json_decode($encodedItem)); + $repositoryCollection->addItem($itemObject, $itemObject->getId()); + } + } + } + return $repositoryCollection; + + } catch (ServerException $serverError) { + return $repositoryCollection; + } + } + + public function getMaxPageCount() { + return $this->maxPageCount; + } + + public function getItemsPerPage() { + return $this->itemsPerPage; + } + +} diff --git a/src/Client.php b/src/Client.php new file mode 100644 index 0000000..de5a69e --- /dev/null +++ b/src/Client.php @@ -0,0 +1,147 @@ +endsWith($giteaURL, '/')) { + $giteaURL += "/"; + } + $this->giteaURL = $giteaURL; + $this->authToken = $authToken; + + // Create a new Guzzle Client + $this->guzzleClient = new GuzzleClient(['base_uri' => $giteaURL]); + } + + public function getGuzzleClient() + { + return $this->guzzleClient; + } + + public function getAuthToken() { + return $this->authToken; + } + + public function setAuthToken($value) { + $this->authToken = $value; + return $this; + } + + public function getBaseURL() + { + return $this->giteaURL; + } + + /** + * Return true if first string ends with the second string. + * Otherwise return false + * + * @author Benjamin Blake (sitelease.ca) + * + * @param string $haystack + * @param string $needle + * @return boolean + */ + function endsWith($haystack, $needle) + { + return substr($haystack, -strlen($needle))===$needle; + } + + /** + * Checks to make sure the auth token is in the right format + * + * Will raise errors if the token is in the wrong format + * + * @author Benjamin Blake (sitelease.ca) + * + * @return boolean + */ + public function checkAuthToken($softErrors = false) { + $authToken = $this->authToken; + if (!$authToken) { + if ($softErrors) { + print("No authentication token was passed in"."\n"); + } else { + trigger_error("No authentication token was passed in"); + } + return false; + } + + if (!strlen($authToken) >= 40) { + if ($softErrors) { + print("The authentication token is too short, it must be at lease 40 characters long"."\n"); + } else { + trigger_error("The authentication token is too short, it must be at lease 40 characters long"); + } + return false; + } + + return true; + } + + /** + * Return the Repositories api object + * + * @author Benjamin Blake (sitelease.ca) + * + * @return Repositories + */ + public function repositories() + { + return new Repositories($this, $this->getAuthToken()); + } + + /** + * Return the Repositories api object + * + * @author Benjamin Blake (sitelease.ca) + * + * @return Organizations + */ + public function organizations() + { + return new Organizations($this, $this->getAuthToken()); + } + +} diff --git a/src/Collections/ApiCollectionInterface.php b/src/Collections/ApiCollectionInterface.php new file mode 100644 index 0000000..769284c --- /dev/null +++ b/src/Collections/ApiCollectionInterface.php @@ -0,0 +1,86 @@ +items = $internalArray; + } + } + + public function addItem($apiObject, $key = null) { + if ($key == null) { + $this->items[] = $apiObject; + } + else { + if (isset($this->items[$key])) { + return false; + } else { + $this->items[$key] = $apiObject; + } + } + } + + public function deleteItem($key) { + if (isset($this->items[$key])) { + unset($this->items[$key]); + } else { + return false; + } + } + + public function getItem($key) { + if (isset($this->items[$key])) { + return $this->items[$key]; + } else { + return false; + } + } + + public function keys() { + return array_keys($this->items); + } + + public function count() { + return count($this->items); + } + + public function keyExists($key) { + return isset($this->items[$key]); + } + + public function toArray() { + return $this->items; + } + + public function getIterator() { + return new ArrayIterator($this->items); + } +} diff --git a/src/Model/Organization.php b/src/Model/Organization.php new file mode 100644 index 0000000..a7aaee7 --- /dev/null +++ b/src/Model/Organization.php @@ -0,0 +1,149 @@ +setUserName($username); + $this->setVisibility($visibility); + } + + /** + * Creates a new organization from the specified JSON map. + * @param object $map A JSON map representing an organization. + * @return static The instance corresponding to the specified JSON map. + */ + static function fromJson(object $map): self { + return (new static(isset($map->username) && is_string($map->username) ? $map->username : '', isset($map->visibility) && is_string($map->visibility) ? $map->visibility : 'private')) + ->setAvatarURL(isset($map->avatar_url) && is_string($map->avatar_url) ? new Uri($map->avatar_url) : null) + ->setDescription(isset($map->description) && is_string($map->description) ? $map->description : '') + ->setFullName(isset($map->full_name) && is_string($map->full_name) ? $map->full_name : '') + ->setLocation(isset($map->location) && is_string($map->location) ? $map->location : '') + ->setWebsite(isset($map->website) && is_string($map->website) ? new Uri($map->website) : null); + } + + /** + * Converts this object to a map in JSON format. + * @return \stdClass The map in JSON format corresponding to this object. + */ + function jsonSerialize(): \stdClass { + return (object) [ + 'avatar_url' => ($url = $this->getAvatarURL()) ? (string) $url : null, + 'description' => $this->getDescription(), + 'full_name' => $this->getFullName(), + 'id' => $this->getId(), + 'location' => $this->getLocation(), + 'username' => $this->getUsername(), + 'visibility' => $this->getVisibility(), + 'website' => ($url = $this->getWebsite()) ? (string) $url : null + ]; + } + + /** + * Gets the organization identifier. + * @return int The organization identifier. + */ + function getId(): int { + return $this->id; + } + + public function getAvatarURL(): ?UriInterface { + return $this->avatarURL; + } + + public function setAvatarURL(?UriInterface $value): self { + $this->avatarURL = $value; + return $this; + } + + public function getDescription() { + return $this->description; + } + + public function setDescription(string $value): self { + $this->description = $value; + return $this; + } + + public function getFullName() { + return $this->fullName; + } + + public function setFullName(string $value): self { + $this->fullName = $value; + return $this; + } + + public function getLocation() { + return $this->location; + } + + public function setLocation(string $value): self { + $this->location = $value; + return $this; + } + + public function getUsername() { + return $this->username; + } + + public function setUsername(string $value): self { + $this->username = $value; + return $this; + } + + public function getVisibility() { + return $this->visibility; + } + + public function setVisibility(string $value): self { + $this->visibility = $value; + return $this; + } + + public function getWebsite(): ?UriInterface { + return $this->website; + } + + public function setWebsite(?UriInterface $value): self { + $this->website = $value; + return $this; + } + +} diff --git a/src/models/PayloadCommit.php b/src/Model/PayloadCommit.php similarity index 100% rename from src/models/PayloadCommit.php rename to src/Model/PayloadCommit.php diff --git a/src/models/PayloadCommitVerification.php b/src/Model/PayloadCommitVerification.php similarity index 100% rename from src/models/PayloadCommitVerification.php rename to src/Model/PayloadCommitVerification.php diff --git a/src/models/PayloadUser.php b/src/Model/PayloadUser.php similarity index 100% rename from src/models/PayloadUser.php rename to src/Model/PayloadUser.php diff --git a/src/models/Permission.php b/src/Model/Permission.php similarity index 100% rename from src/models/Permission.php rename to src/Model/Permission.php diff --git a/src/models/Repository.php b/src/Model/Repository.php similarity index 96% rename from src/models/Repository.php rename to src/Model/Repository.php index d99a474..e6786bb 100644 --- a/src/models/Repository.php +++ b/src/Model/Repository.php @@ -81,7 +81,7 @@ class Repository implements \JsonSerializable { * @param int $id The repository identifier. * @param string $fullName The full name of the repository. */ - function __construct(int $id, string $fullName) { + function __construct(int $id, string $fullName, object $giteaClient = null) { $this->id = $id; $this->setFullName($fullName); } @@ -552,4 +552,38 @@ class Repository implements \JsonSerializable { $this->website = $value; return $this; } + + /** + * Undocumented function + * + * Call: + * $giteaClient->projects->fetchall + * + * @author Benjamin Blake (sitelease.ca) + * + * @param Type $var + * @return void + */ + public static function getAll(Type $var = null) + { + + } + + /** + * Undocumented function + * + * Example: + * ``` + * $giteaClient->repositories->fetchall(); + * ``` + * + * @author Benjamin Blake (sitelease.ca) + * + * @param Type $var + * @return void + */ + public function get(Type $var = null) + { + + } } diff --git a/src/models/ServerVersion.php b/src/Model/ServerVersion.php similarity index 100% rename from src/models/ServerVersion.php rename to src/Model/ServerVersion.php diff --git a/src/models/StatusState.php b/src/Model/StatusState.php similarity index 100% rename from src/models/StatusState.php rename to src/Model/StatusState.php diff --git a/src/models/Team.php b/src/Model/Team.php similarity index 100% rename from src/models/Team.php rename to src/Model/Team.php diff --git a/src/models/TeamPermission.php b/src/Model/TeamPermission.php similarity index 100% rename from src/models/TeamPermission.php rename to src/Model/TeamPermission.php diff --git a/src/models/TrackedTime.php b/src/Model/TrackedTime.php similarity index 100% rename from src/models/TrackedTime.php rename to src/Model/TrackedTime.php diff --git a/src/models/User.php b/src/Model/User.php similarity index 100% rename from src/models/User.php rename to src/Model/User.php diff --git a/src/TestRun.php b/src/TestRun.php new file mode 100644 index 0000000..e10472f --- /dev/null +++ b/src/TestRun.php @@ -0,0 +1,39 @@ +setAuthToken('32e609ad39539c1d0e8544800bd49b1025ac6b49'); +// print("Getting Organization via API \n"); +// $organization = $giteaClient->organizations()->getByUsername("Sitelease"); +// if ($organization) { +// print("Username: ".$organization->getUsername()."\n"); +// print("Full Name: ".$organization->getFullName()."\n"); +// print("Description: ".$organization->getDescription()."\n"); +// print("Website: ".$organization->getWebsite()."\n"); +// print("Location: ".$organization->getLocation()."\n"); +// var_dump(json_encode($organization)); +// } else { +// print("No Data could be retrieved"."\n"); +// } +print("Getting all repos via API \n"); +$repositories = $giteaClient->repositories()->all(); +if ($repositories && count($repositories) > 0) { + foreach ($repositories as $item) { + print("Name: ".$item->getName()."\n"); + print("Full Name: ".$item->getFullName()."\n"); + print("Description: ".$item->getDescription()."\n"); + print("\n\n"); + } + print("Total Items: ".count($repositories)."\n"); +} else { + print("No repos could be retrieved"."\n"); +} + +print("Exiting script"."\n");