From 150559b075370154252f85f03cf09206a24bf9d2 Mon Sep 17 00:00:00 2001 From: Victor Westerlund Date: Sat, 6 Apr 2024 22:45:33 +0000 Subject: [PATCH] feat: major post-launch fixes and bump of `reflect/client` to 3.0.6 (#9) --- api/composer.json | 11 +- api/composer.lock | 111 +++++---------- api/endpoints/search/GET.php | 16 ++- api/endpoints/work/GET.php | 28 +--- api/endpoints/work/actions/GET.php | 67 ++++++++++ api/src/databases/VLWdb.php | 2 + api/src/packages/Endpoints/composer.json | 17 +++ api/src/packages/Endpoints/src/Endpoints.php | 11 ++ assets/css/document.css | 20 ++- assets/css/pages/about.css | 19 +-- assets/css/pages/contact.css | 3 - assets/css/pages/search.css | 29 ++-- assets/css/pages/work.css | 16 +-- composer.json | 16 ++- composer.lock | 74 ++++++++-- pages/about.php | 27 ---- pages/contact.php | 26 ++-- pages/search.php | 134 ++++++++++++------- pages/work.php | 12 +- src/packages/API/composer.json | 20 +++ src/packages/API/composer.lock | 58 ++++++++ src/packages/API/src/Client.php | 18 +++ 22 files changed, 471 insertions(+), 264 deletions(-) create mode 100644 api/endpoints/work/actions/GET.php create mode 100644 api/src/packages/Endpoints/composer.json create mode 100644 api/src/packages/Endpoints/src/Endpoints.php create mode 100644 src/packages/API/composer.json create mode 100644 src/packages/API/composer.lock create mode 100644 src/packages/API/src/Client.php diff --git a/api/composer.json b/api/composer.json index c85dbd6..4e00ddc 100755 --- a/api/composer.json +++ b/api/composer.json @@ -1,7 +1,14 @@ { "require": { + "local/api.endpoints": "1.0.0-dev", "reflect/plugin-rules": "^1.5", - "victorwesterlund/innodb-fk": "^1.0", "victorwesterlund/xenum": "^1.1" - } + }, + "minimum-stability": "dev", + "repositories": [ + { + "type": "path", + "url": "src/packages/Endpoints" + } + ] } diff --git a/api/composer.lock b/api/composer.lock index 46c636a..aaeccbf 100755 --- a/api/composer.lock +++ b/api/composer.lock @@ -4,8 +4,33 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ba3fa8466aa20501e06050d722c86a35", + "content-hash": "9da96ba90ef20d885034442b30dce0a3", "packages": [ + { + "name": "local/api.endpoints", + "version": "1.0.0-dev", + "dist": { + "type": "path", + "url": "src/packages/Endpoints", + "reference": "89b7b9a4cc504abddb4aeec8e05a95c9d9087575" + }, + "type": "library", + "autoload": { + "psr-4": { + "VLW\\API\\": "src/" + } + }, + "authors": [ + { + "name": "Victor Westerlund", + "email": "victor@vlw.se" + } + ], + "description": "Endpoint pathmappings for VLW API", + "transport-options": { + "relative": true + } + }, { "name": "reflect/plugin-rules", "version": "1.5.0", @@ -43,84 +68,6 @@ }, "time": "2024-01-17T11:07:44+00:00" }, - { - "name": "victorwesterlund/innodb-fk", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/VictorWesterlund/php-libinnodb-fk.git", - "reference": "ffea024f16613e6d6857c93200185cf0a20a9640" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/VictorWesterlund/php-libinnodb-fk/zipball/ffea024f16613e6d6857c93200185cf0a20a9640", - "reference": "ffea024f16613e6d6857c93200185cf0a20a9640", - "shasum": "" - }, - "require": { - "victorwesterlund/libmysqldriver": "^3.0", - "victorwesterlund/xenum": "^1.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "victorwesterlund\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-3.0-only" - ], - "authors": [ - { - "name": "Victor Westerlund", - "email": "victor.vesterlund@gmail.com" - } - ], - "description": "Retrievie and optionally resolves foreign keys in a MySQL/MariaDB InnoDB database", - "support": { - "issues": "https://github.com/VictorWesterlund/php-libinnodb-fk/issues", - "source": "https://github.com/VictorWesterlund/php-libinnodb-fk/tree/1.0.3" - }, - "time": "2023-11-02T13:26:34+00:00" - }, - { - "name": "victorwesterlund/libmysqldriver", - "version": "3.5.1", - "source": { - "type": "git", - "url": "https://github.com/VictorWesterlund/php-libmysqldriver.git", - "reference": "73b5d858ffa8d83c5cbe6b3d3de4af314a5ffbe5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/VictorWesterlund/php-libmysqldriver/zipball/73b5d858ffa8d83c5cbe6b3d3de4af314a5ffbe5", - "reference": "73b5d858ffa8d83c5cbe6b3d3de4af314a5ffbe5", - "shasum": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "libmysqldriver\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-3.0-only" - ], - "authors": [ - { - "name": "Victor Westerlund", - "email": "victor.vesterlund@gmail.com" - } - ], - "description": "Abstraction library for common mysqli features", - "support": { - "issues": "https://github.com/VictorWesterlund/php-libmysqldriver/issues", - "source": "https://github.com/VictorWesterlund/php-libmysqldriver/tree/3.5.1" - }, - "time": "2024-02-26T12:51:52+00:00" - }, { "name": "victorwesterlund/xenum", "version": "1.1.1", @@ -161,8 +108,10 @@ ], "packages-dev": [], "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], + "minimum-stability": "dev", + "stability-flags": { + "local/api.endpoints": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": [], diff --git a/api/endpoints/search/GET.php b/api/endpoints/search/GET.php index 564f8b0..3a39896 100755 --- a/api/endpoints/search/GET.php +++ b/api/endpoints/search/GET.php @@ -1,17 +1,21 @@ value => true ]; - return $this->search(WorkModel::TABLE, $search, $conditions); + $results = $this->search(WorkModel::TABLE, $search, $conditions); + + foreach ($results as &$result) { + $result["actions"] = (new Call(Endpoints::WORK_ACTIONS->value)) + ->params([WorkActionsModel::ANCHOR->value => $result[WorkModel::ID->value]]) + ->get()->output(); + } + + return $results; } // # Responses diff --git a/api/endpoints/work/GET.php b/api/endpoints/work/GET.php index a8ef1d1..3a2fe0e 100755 --- a/api/endpoints/work/GET.php +++ b/api/endpoints/work/GET.php @@ -1,13 +1,14 @@ fetch_all(MYSQLI_ASSOC) : []; } - private function fetch_row_actions(string $id): array { - $resp = $this->db->for(WorkActionsModel::TABLE) - ->where([WorkActionsModel::ANCHOR->value => $id]) - ->select([ - WorkActionsModel::DISPLAY_TEXT->value, - WorkActionsModel::HREF->value, - WorkActionsModel::CLASS_LIST->value, - WorkActionsModel::EXTERNAL->value - ]); - - return parent::is_mysqli_result($resp) ? $resp->fetch_all(MYSQLI_ASSOC) : []; - } - // # Responses // Return 422 Unprocessable Content error if request validation failed @@ -134,13 +122,11 @@ $rows = []; while ($row = $resp->fetch_assoc()) { $row["tags"] = $this->fetch_row_tags($row["id"]); - $row["actions"] = $this->fetch_row_actions($row["id"]); - // Resolve media entities in srcset - $srcset = Call("media/srcset?id={$row[WorkModel::COVER_SRCSET->value]}", Method::GET); - - // Mutate key on current row - $row[WorkModel::COVER_SRCSET->value] = $srcset->ok ? $srcset->output() : []; + // Fetch actions for work entity by id from endpoint + $row["actions"] = (new Call(Endpoints::WORK_ACTIONS->value)) + ->params([WorkActionsModel::ANCHOR->value => $row[WorkModel::ID->value]]) + ->get()->output(); $rows[] = $row; } diff --git a/api/endpoints/work/actions/GET.php b/api/endpoints/work/actions/GET.php new file mode 100644 index 0000000..616dff9 --- /dev/null +++ b/api/endpoints/work/actions/GET.php @@ -0,0 +1,67 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->GET([ + (new Rules(WorkActionsModel::ANCHOR->value)) + ->required() + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) + ]); + } + + // # Responses + + // Return 422 Unprocessable Content error if request validation failed + private function resp_rules_invalid(): Response { + return new Response($this->ruleset->get_errors(), 422); + } + + // Return a 503 Service Unavailable error if something went wrong with the database call + private function resp_database_error(): Response { + return new Response("Failed to get work data, please try again later", 503); + } + + public function main(): Response { + // Bail out if request validation failed + if (!$this->ruleset->is_valid()) { + return $this->resp_rules_invalid(); + } + + $resp = $this->db->for(WorkActionsModel::TABLE) + ->where([WorkActionsModel::ANCHOR->value => $_GET[WorkActionsModel::ANCHOR->value]]) + ->select([ + WorkActionsModel::DISPLAY_TEXT->value, + WorkActionsModel::HREF->value, + WorkActionsModel::CLASS_LIST->value, + WorkActionsModel::EXTERNAL->value + ]); + + // Bail out if something went wrong retrieving rows from the database + if (!parent::is_mysqli_result($resp)) { + return $this->resp_database_error(); + } + + return $resp->num_rows > 0 + ? new Response($resp->fetch_all(MYSQLI_ASSOC)) + : new Response([]); + } + } \ No newline at end of file diff --git a/api/src/databases/VLWdb.php b/api/src/databases/VLWdb.php index bb1c4ca..f7fcad6 100755 --- a/api/src/databases/VLWdb.php +++ b/api/src/databases/VLWdb.php @@ -5,6 +5,8 @@ use libmysqldriver\MySQL; class VLWdb { + const UUID_LENGTH = 36; + const MYSQL_TEXT_MAX_LENGTH = 65538; const MYSQL_VARCHAR_MAX_LENGTH = 255; const MYSQL_INT_MAX_LENGHT = 2147483647; diff --git a/api/src/packages/Endpoints/composer.json b/api/src/packages/Endpoints/composer.json new file mode 100644 index 0000000..191ca06 --- /dev/null +++ b/api/src/packages/Endpoints/composer.json @@ -0,0 +1,17 @@ +{ + "name": "local/api.endpoints", + "description": "Endpoint pathmappings for VLW API", + "type": "library", + "version": "1.0.0-dev", + "authors": [ + { + "name": "Victor Westerlund", + "email": "victor@vlw.se" + } + ], + "autoload": { + "psr-4": { + "VLW\\API\\": "src/" + } + } +} diff --git a/api/src/packages/Endpoints/src/Endpoints.php b/api/src/packages/Endpoints/src/Endpoints.php new file mode 100644 index 0000000..95bfeb5 --- /dev/null +++ b/api/src/packages/Endpoints/src/Endpoints.php @@ -0,0 +1,11 @@ + hr { @@ -38,12 +27,8 @@ section.about { gap: calc(var(--padding) / 2); } -section.about { - font-size: 16px; -} - section.about p:first-of-type:first-letter { - font-size: 1.5rem; + font-size: 1.8rem; font-weight: bold; margin-right: .1rem; color: var(--color-accent); @@ -73,7 +58,7 @@ div.interests { height: 100%; font-weight: bold; pointer-events: none; - font-size: 60px; + font-size: 50px; color: var(--color-accent); overflow: hidden; opacity: 0; diff --git a/assets/css/pages/contact.css b/assets/css/pages/contact.css index 88ef8a5..ec6014d 100755 --- a/assets/css/pages/contact.css +++ b/assets/css/pages/contact.css @@ -46,7 +46,6 @@ section.social social p { transform: translate(0, 0); background-color: rgba(var(--primer-color-accent), .1); padding: 5px 10px; - font-size: 17px; white-space: nowrap; pointer-events: none; border-radius: 6px; @@ -98,7 +97,6 @@ section.form :is(input, textarea) { min-width: 100%; max-width: 100%; color: black; - font-size: 15px; padding: var(--padding); border-radius: 4px; border: none; @@ -160,7 +158,6 @@ section.form-message h3 { } section.form-message pre { - font-size: 11px; padding: var(--padding); background-color: rgba(0, 0, 0, .15); } diff --git a/assets/css/pages/search.css b/assets/css/pages/search.css index 4067eab..e81316d 100755 --- a/assets/css/pages/search.css +++ b/assets/css/pages/search.css @@ -38,12 +38,24 @@ section.search button[type="submit"] { max-width: 350px; } +section.search > svg { + width: 100%; +} + body:not([vv-page="/search"]) section.search { display: none; } /* # Search results */ +section.results .result { + display: flex; + flex-direction: column; + gap: calc(var(--padding) / 2); +} + +/* ---- */ + main > svg, dialog.search search search-results > svg { margin: auto; @@ -57,7 +69,7 @@ dialog.search search search-results .empty { /* ## Titles */ -section.title h2 { +section.title a h2 { color: var(--color-accent); } @@ -76,19 +88,6 @@ section.results.work { section.results.work .result { padding: var(--padding); - background-color: rgba(255, 255, 255, .1); + background-color: rgba(255, 255, 255, .03); border-radius: 6px; -} - -/* # Feature queries */ - -@media (hover: hover) { - section.results.work .result { - transition: 300ms background-color; - } - - section.results.work .result:hover { - background-color: rgba(255, 255, 255, .2); - box-shadow: 0 5px 70px 10px rgba(0, 0, 0, .3); - } } \ No newline at end of file diff --git a/assets/css/pages/work.css b/assets/css/pages/work.css index 2bfd42b..c39860c 100755 --- a/assets/css/pages/work.css +++ b/assets/css/pages/work.css @@ -9,7 +9,6 @@ main { display: flex; flex-direction: column; gap: var(--padding); - padding: calc(var(--padding) * 1.5); width: 100%; max-width: 1200px; overflow-x: initial; @@ -114,18 +113,6 @@ section.timeline .items .item .tags .tag { padding: 5px 10px; } -section.timeline .items .item h2 { - font-size: 30px; -} - -section.timeline .items .item h3 { - font-size: 25px; -} - -section.timeline .items .item p { - font-size: 16px; -} - section.timeline .items .item img { max-width: 100%; height: 250px; @@ -133,7 +120,6 @@ section.timeline .items .item img { section.timeline .items .item .actions { margin-top: 7px; - font-size: 13px; } /* ## Note */ @@ -174,7 +160,6 @@ section.note { section.timeline .track { position: relative; - left: calc(var(--padding) * 1.5); background: unset; z-index: 10; pointer-events: none; @@ -194,6 +179,7 @@ section.note { } section.timeline .items .item { + padding: calc(var(--padding) * 1.5) 0; width: calc(100vw - (var(--padding) * 3.5)); } diff --git a/composer.json b/composer.json index e88fad0..7724816 100755 --- a/composer.json +++ b/composer.json @@ -1,5 +1,17 @@ { "require": { - "reflect/client": "^2.1" - } + "local/api.client": "1.0.0-dev", + "local/api.endpoints": "1.0.0-dev" + }, + "minimum-stability": "dev", + "repositories": [ + { + "type": "path", + "url": "src/packages/API" + }, + { + "type": "path", + "url": "api/src/packages/Endpoints" + } + ] } diff --git a/composer.lock b/composer.lock index e0e45b8..5e24289 100755 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,73 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3e10269d358ba18734f4f011cd22c42e", + "content-hash": "73a61bf0308871f9dc9ad050aedfe13e", "packages": [ + { + "name": "local/api.client", + "version": "1.0.0-dev", + "dist": { + "type": "path", + "url": "src/packages/API", + "reference": "020275feb0e0017fa91ae0b33213bc54f35cac75" + }, + "require": { + "reflect/client": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "VLW\\API\\": "src/" + } + }, + "authors": [ + { + "name": "Victor Westerlund", + "email": "victor@vlw.se" + } + ], + "description": "Wrapper for vlw.se API", + "transport-options": { + "relative": true + } + }, + { + "name": "local/api.endpoints", + "version": "1.0.0-dev", + "dist": { + "type": "path", + "url": "api/src/packages/Endpoints", + "reference": "89b7b9a4cc504abddb4aeec8e05a95c9d9087575" + }, + "type": "library", + "autoload": { + "psr-4": { + "VLW\\API\\": "src/" + } + }, + "authors": [ + { + "name": "Victor Westerlund", + "email": "victor@vlw.se" + } + ], + "description": "Endpoint pathmappings for VLW API", + "transport-options": { + "relative": true + } + }, { "name": "reflect/client", - "version": "2.1.4", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/VictorWesterlund/reflect-client-php.git", - "reference": "47cee961d1bfdd9261a58dde753d824947e91636" + "reference": "89a8c041044c8c60cefafc4716d5d61b96c43e06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/VictorWesterlund/reflect-client-php/zipball/47cee961d1bfdd9261a58dde753d824947e91636", - "reference": "47cee961d1bfdd9261a58dde753d824947e91636", + "url": "https://api.github.com/repos/VictorWesterlund/reflect-client-php/zipball/89a8c041044c8c60cefafc4716d5d61b96c43e06", + "reference": "89a8c041044c8c60cefafc4716d5d61b96c43e06", "shasum": "" }, "type": "library", @@ -39,15 +92,18 @@ "description": "Extendable PHP interface for communicating with Reflect API over HTTP or UNIX sockets", "support": { "issues": "https://github.com/VictorWesterlund/reflect-client-php/issues", - "source": "https://github.com/VictorWesterlund/reflect-client-php/tree/2.1.4" + "source": "https://github.com/VictorWesterlund/reflect-client-php/tree/3.0.6" }, - "time": "2023-08-18T14:41:31+00:00" + "time": "2024-04-06T14:55:04+00:00" } ], "packages-dev": [], "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], + "minimum-stability": "dev", + "stability-flags": { + "local/api.client": 20, + "local/api.endpoints": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": [], diff --git a/pages/about.php b/pages/about.php index ade45ed..a5f9ccd 100755 --- a/pages/about.php +++ b/pages/about.php @@ -1,30 +1,3 @@ -call("/coffee", Method::GET); - - $offset = 86400; // 24 hours in seconds - $now = time(); - - // Get only timestamps from response - $coffee_dates = array_column($resp[1], "date_timestamp_created"); - // Filter array for timestamps between now and $offset - $coffee_last_day = array_filter($coffee_dates, fn(int $time): bool => $time >= ($now - $offset)); - - return count($coffee_last_day); - } - -?>
diff --git a/pages/contact.php b/pages/contact.php index 749ff82..04295d4 100755 --- a/pages/contact.php +++ b/pages/contact.php @@ -1,18 +1,26 @@ call("messages", Method::POST, $_POST); - - // Set message sent to true if ok, false if something went wrong - $message_sent = $post_message[0] === 201; + // Submit message to endpoint and set variable with results + $message_sent = $api->call(Endpoints::MESSAGES->value)->post([ + ContactFieldsEnum::EMAIL->value => $_POST[ContactFieldsEnum::EMAIL->value], + ContactFieldsEnum::MESSAGE->value => $_POST[ContactFieldsEnum::MESSAGE->value] + ])->ok; } ?> @@ -63,11 +71,11 @@
- + - +
diff --git a/pages/search.php b/pages/search.php index 91910b0..64f6776 100755 --- a/pages/search.php +++ b/pages/search.php @@ -1,26 +1,24 @@ call("/search?q={$query}", Method::GET) : null; - - // ISO 8601: YYYY-MM-DD - $date_format = "Y-m-d"; + // Get search results from endpoint + $response = $api->call(Endpoints::SEARCH->value) + // Get query string from search parameter if set + ->params(["q" => $query]) + ->get(); ?> - + - + json(); ?> - - + + code): default: ?> + +
+

Something went wrong

+
+ - - 0): ?> - - + + - - -
-

Work

-

search result(s) from my public work

-
-
- - " vv="search" vv-call="navigate">
-

-

-

-
- + + 0): ?> + + + + + +
+

Work

+

search result(s) from my public work

+
+
+ + + +
+

+

+

+ + + + + + +
+ + +
+ + + + +
+

No results for search term ""

- - -
-

No results for search term ""

+ + + + +
+

Connection to VLW API was successful but lacking permission to search

- + - - - - - + + $error_msg): ?> @@ -91,13 +125,9 @@ - - -
-

Something went wrong

-
- - + + + diff --git a/pages/work.php b/pages/work.php index 0636062..571a286 100755 --- a/pages/work.php +++ b/pages/work.php @@ -1,13 +1,13 @@ call("/work", Method::GET); + $response = $api->call(Endpoints::WORK->value)->get(); ?> @@ -21,7 +21,7 @@
- +ok): ?> json() as $row) { // Create array for current year if it doesn't exist if (!array_key_exists($row["date_year"], $rows)) { $rows[$row["date_year"]] = []; diff --git a/src/packages/API/composer.json b/src/packages/API/composer.json new file mode 100644 index 0000000..2edfca6 --- /dev/null +++ b/src/packages/API/composer.json @@ -0,0 +1,20 @@ +{ + "name": "local/api.client", + "description": "Wrapper for vlw.se API", + "type": "library", + "version": "1.0.0-dev", + "authors": [ + { + "name": "Victor Westerlund", + "email": "victor@vlw.se" + } + ], + "autoload": { + "psr-4": { + "VLW\\API\\": "src/" + } + }, + "require": { + "reflect/client": "^3.0" + } +} diff --git a/src/packages/API/composer.lock b/src/packages/API/composer.lock new file mode 100644 index 0000000..2896728 --- /dev/null +++ b/src/packages/API/composer.lock @@ -0,0 +1,58 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "ea73f1cfa968f06be9c2ac54d2a56c96", + "packages": [ + { + "name": "reflect/client", + "version": "dev-feat/responseobj", + "source": { + "type": "git", + "url": "https://github.com/VictorWesterlund/reflect-client-php.git", + "reference": "228b3c665d4af5023ac4ed71fe32bb144521b43b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/VictorWesterlund/reflect-client-php/zipball/228b3c665d4af5023ac4ed71fe32bb144521b43b", + "reference": "228b3c665d4af5023ac4ed71fe32bb144521b43b", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Reflect\\": "src/Reflect/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-only" + ], + "authors": [ + { + "name": "Victor Westerlund", + "email": "victor.vesterlund@gmail.com" + } + ], + "description": "Extendable PHP interface for communicating with Reflect API over HTTP or UNIX sockets", + "support": { + "issues": "https://github.com/VictorWesterlund/reflect-client-php/issues", + "source": "https://github.com/VictorWesterlund/reflect-client-php/tree/feat/responseobj" + }, + "time": "2024-03-18T11:57:57+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "reflect/client": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.0.0" +} diff --git a/src/packages/API/src/Client.php b/src/packages/API/src/Client.php new file mode 100644 index 0000000..2f5dd7d --- /dev/null +++ b/src/packages/API/src/Client.php @@ -0,0 +1,18 @@ +