diff --git a/.env.example.ini b/.env.example.ini index 8d9449e..a214a34 100755 --- a/.env.example.ini +++ b/.env.example.ini @@ -1,7 +1,20 @@ -[api] -base_url = "https://api.vlw.one/" +[client_api] +base_url = "" api_key = "" -verify_peer = 0 +verify_peer = true -[time] -date_time_zone = "Europe/Stockholm" \ No newline at end of file +[client_time_available] +time_zone = "Europe/Stockholm" +available_to_hour = 0; +reply_average_hours = 0; +available_from_hour = 0; + +[server_database] +host = "" +user = "" +pass = "" +db = "" + +[server_forgejo] +base_url = "" +scan_profiles = "" \ No newline at end of file diff --git a/.gitignore b/.gitignore index f3abf1c..809e797 100755 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,7 @@ # Public assets # ################# -public/robots.txt public/.well-known - -assets/js/modules/npm +public/assets/js/modules/npm # Bootstrapping # ################# diff --git a/README.md b/README.md index e3b6466..988f7b3 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,66 @@ # vlw.se -This is the source code behind [vlw.se](https://vlw.se) which has been written from the ground up by me. This website is built on top of my [Vegvisir web framework](https://vegvisir.vlw.se) and my [Reflect API framework](https://reflect.vlw.se). +This is the source code behind [vlw.se](https://vlw.se) which is my personal website that I have written and designed from the ground up. The website is built on top of my own [web framework](https://vegvisir.vlw.se) and its API is also built on top of my own [API framework](https://reflect.vlw.se). # Installation -If you for whatever reason want to get this website up and running for yourself this is how that is done. +Here's how you get my website up and running on your own machine. Note, I have only tested this on Linux and the install script we will run later is written in bash. -## This website requires the following prerequisites -- [PHP 8.0+](https://www.php.net/) -- [MariaDB 14+](https://mariadb.org/) -- [The NPM package manager](https://www.npmjs.com/) -- [The Reflect API framework](https://reflect.vlw.se) -- [The Vegvisir web framework](https://vegvisir.vlw.se) -- [The composer package manager](https://getcomposer.org/) +**Make sure you have both of these package managers installed before proceeding:** +- [Composer](https://getcomposer.org/) +- [NPM](https://www.npmjs.com/) -**Confimed supported framework versions:** -Vegvisir|Reflect ---|-- -✅ [`3.1.0`](https://codeberg.org/vegvisir/vegvisir/releases/tag/3.1.0)|✅ [`2.7.2`](https://codeberg.org/reflect/reflect/releases/tag/2.7.2) +## 1. Clone this repo +Clone/download this repo to your machine. Preferably to a non-public directory - the frameworks will handle that. -## Website (Vegvisir) -1. **Download this repo** - - Git clone or download this repo to any local folder - ``` - git clone https://codeberg.org/vlw/vlw.se - ``` -2. **Download and install Vegvisir** - - Follow the installation instructions for [Vegvisir](https://vegvisir.vlw.se/docs/installation) and point the `root_path` variable to your local vlw.se folder. +``` +git clone https://codeberg.org/vlw/vlw.se --depth 1 +``` -3. **Run the install script** +## 2. Install [Vegvisir](https://vegvisir.vlw.se) and [Reflect](https://reflect.vlw.se) +Follow the installation instructions for my web, and API framework. This site uses the default configuration for both frameworks so the only thing you need to do after you've installed both is to point the `root_path` and `endpoints` directory respectively to the directory where you cloned this repo. - This bash script will install dependencies and make npm modules public. - ``` - ./install.sh - ``` +- [Vegvisir installation](https://vegvisir.vlw.se) +- [Reflect installation](https://reflect.vlw.se) -Et voila! You probably want to install the API-side too but the website itself should now be accessible from your configured Vegvisir host. +*Example:* +```sh +# Vegvisir +root_path = "/var/www/vlw.se" +# Reflect +endpoints = "/var/www/vlw.se" +``` -## API (Reflect) -The API (and database) is where most content is stored and served from on this website. +## 3. Run the install script +Run the `install.sh` script from the root of the repo directory. [Make sure you have the required package managers installed](#installation). -1. **Download this repo** +**Example:** +```sh +# vlw@example:$ +cd /var/www/vlw.se +# vlw@example:/var/www/vlw.se$ +./install.sh +``` - **You can skip this if you've already downloaded the repo from step 1 in the website installation.** +## 4. Import the database templates +There's are two SQL files that you can download from the releases page that has a snapshot of the MariaDB databases I use on my live website. The snapshot data for the website databse is not guaranteed to be up to date; but the database structure will be. Download and import these files into two existing databases. One for the website data, and the other has the Reflect API configurations. - Otherwise... Git clone or download this repo to any local folder - ``` - git clone https://codeberg.org/vlw/vlw.se - ``` +- [Download SQL-snapshots](https://codeberg.org/vlw/vlw.se/releases) -2. **Download and install Reflect** - - Follow the installation instructions for [Reflect](https://reflect.vlw.se/docs/installation) and point the `endpoints` variable to the `/api` subdirectory in the local vlw.se folder. +## 5. Set environment variables +Make a copy of the `.env.example.ini` file called `.env.ini` from the root directory of the repo. There are a few parameters you can change here but the required ones are the following: -3. **Install dependencies** +```ini +[client_api] +base_url = "" +api_key = "" - `cd` into the api folder and install dependencies with composer. - ``` - composer install --optimize-autoloader - ``` +[server_database] +host = "" +user = "" +pass = "" +db = "" +``` -4. **Create and import database** +Please refer to the comments in the ini file for more information about each field. - [Create and] import the two databases associated with vlw.se data and the Reflect API configurations from `.sql` files on the Releases page. - -5. **Set environment variables** - - Make a copy of `/api/.env.example.ini` and change the `[vlwdb]` variables with your MariaDB credentials. - -6. **Set environment variables for website** - - It's reasonable to assume if you've installed the website from this repo that you'd also want to use the API with it. Start my making a copy of `/.env.example.ini` (root directory) and change the `[api]` variables to point to your API hostname. +## Done! +That should be it. Navigate to your configured Vegvisir public host! \ No newline at end of file diff --git a/api/.env.example.ini b/api/.env.example.ini deleted file mode 100755 index 04c3c11..0000000 --- a/api/.env.example.ini +++ /dev/null @@ -1,19 +0,0 @@ -[connect] -host = "" -user = "" -pass = "" - -[databases] -vlw = "" -battlestation = "" - -; Forgejo instance config -[forgejo] -base_url = "" - -; Forgejo language chart endpoints config -[about_languages] -; CSV of Forgejo profiles to include public source repositories from -scan_profiles = "" -; Path to a JSON file to store cached language endpoint responses -cache_file = "" \ No newline at end of file diff --git a/api/composer.json b/api/composer.json deleted file mode 100755 index 16878b5..0000000 --- a/api/composer.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "require": { - "reflect/plugin-rules": "^1.5", - "victorwesterlund/xenum": "dev-master", - "vlw/mysql": "dev-master" - }, - "minimum-stability": "dev" -} diff --git a/api/composer.lock b/api/composer.lock deleted file mode 100755 index 3887c2d..0000000 --- a/api/composer.lock +++ /dev/null @@ -1,115 +0,0 @@ -{ - "_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": "f3f2b3cb3bd789eee6af4a93f4a6e0f9", - "packages": [ - { - "name": "reflect/plugin-rules", - "version": "1.5.1", - "source": { - "type": "git", - "url": "https://codeberg.org/reflect/reflect-rules-plugin", - "reference": "df150f0d860dbc2311e5e2fcb2fac36ee52db56b" - }, - "type": "library", - "autoload": { - "psr-4": { - "ReflectRules\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-3.0-only" - ], - "authors": [ - { - "name": "Victor Westerlund", - "email": "victor.vesterlund@gmail.com" - } - ], - "description": "Add request search paramter and request body constraints to an API built with Reflect", - "time": "2024-11-20T10:39:33+00:00" - }, - { - "name": "victorwesterlund/xenum", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/VictorWesterlund/php-xenum.git", - "reference": "8972f06f42abd1f382807a67e937d5564bb89699" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/VictorWesterlund/php-xenum/zipball/8972f06f42abd1f382807a67e937d5564bb89699", - "reference": "8972f06f42abd1f382807a67e937d5564bb89699", - "shasum": "" - }, - "default-branch": true, - "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": "PHP eXtended Enums. The missing quality-of-life features from PHP 8+ Enums", - "support": { - "issues": "https://github.com/VictorWesterlund/php-xenum/issues", - "source": "https://github.com/VictorWesterlund/php-xenum/tree/1.1.1" - }, - "time": "2023-11-20T10:10:39+00:00" - }, - { - "name": "vlw/mysql", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://codeberg.org/vlw/php-mysql", - "reference": "619f43b3bfab9eb034dca3e54c7466055240c861" - }, - "default-branch": true, - "type": "library", - "autoload": { - "psr-4": { - "vlw\\MySQL\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-3.0-or-later" - ], - "authors": [ - { - "name": "Victor Westerlund", - "email": "victor@vlw.se" - } - ], - "description": "Abstraction library for common MySQL/MariaDB DML operations with php-mysqli", - "time": "2024-09-25T13:28:15+00:00" - } - ], - "packages-dev": [], - "aliases": [], - "minimum-stability": "dev", - "stability-flags": { - "victorwesterlund/xenum": 20, - "vlw/mysql": 20 - }, - "prefer-stable": false, - "prefer-lowest": false, - "platform": [], - "platform-dev": [], - "plugin-api-version": "2.3.0" -} diff --git a/api/endpoints/about/languages/DELETE.php b/api/endpoints/about/languages/DELETE.php deleted file mode 100644 index c7a4dab..0000000 --- a/api/endpoints/about/languages/DELETE.php +++ /dev/null @@ -1,22 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->GET([ - (new Rules(PARM_FORCE_RECACHE)) - ->type(Type::BOOLEAN) - ->default(false) - ]); - - $this->ruleset->validate_or_exit(); - } - - private static function cache_exists(): bool { - return file_exists($_ENV["forgejo_languages"]["cache_file"]); - } - - private static function load_cache(): array { - return json_decode(file_get_contents($_ENV["forgejo_languages"]["cache_file"]), true); - } - - public function main(): Response { - // Delete cache file if force flag is set - if ($_GET[PARM_FORCE_RECACHE]) { - (new Call(Endpoints::ABOUT_LANGUAGES->value))->delete(); - } - - return self::cache_exists() - // Return languages from cache - ? new Response(self::load_cache()) - // Fetch and return languages (and generate cache file if enabled) - : new Response((new Call(Endpoints::ABOUT_LANGUAGES->value))->post()); - } - } \ No newline at end of file diff --git a/api/endpoints/battlestation/gpu/GET.php b/api/endpoints/battlestation/gpu/GET.php deleted file mode 100644 index 5a8598b..0000000 --- a/api/endpoints/battlestation/gpu/GET.php +++ /dev/null @@ -1,107 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->GET([ - (new Rules(GpuModel::ID->value)) - ->type(Type::STRING) - ->min(parent::UUID_LENGTH) - ->max(parent::UUID_LENGTH), - - (new Rules(GpuModel::MEMORY->value)) - ->type(Type::NUMBER) - ->min(1), - - (new Rules(GpuModel::VENDOR_NAME->value)) - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(GpuModel::VENDOR_MODEL->value)) - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(GpuModel::VENDOR_CHIP_NAME->value)) - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(GpuModel::VENDOR_CHIP_MODEL->value)) - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(GpuModel::IS_RETIRED->value)) - ->type(Type::BOOLEAN) - ]); - - parent::__construct(Databases::BATTLESTATION, $this->ruleset); - - // Use a copy of search parameters - $this->query = $_GET; - } - - private function get_motherboards(): void { - foreach ($this->results as &$result) { - // Get motherboard id from relationship by chassis id - $result[self::REL_MOTHERBOARDS] = $this->db - ->for(MbGpuModel::TABLE) - ->where([MbGpuModel::REF_GPU_ID->value => $result[GpuModel::ID->value]]) - ->select(MbGpuModel::values()) - ->fetch_all(MYSQLI_ASSOC); - } - } - - private function get_gpu(): array { - return $this->results = $this->db - ->for(GpuModel::TABLE) - ->where($this->query) - ->order([GpuModel::DATE_AQUIRED->value => "DESC"]) - ->select(GpuModel::values()) - ->fetch_all(MYSQLI_ASSOC); - } - - public function main(): Response { - // Set properties as "searchable" - parent::make_wildcard_search(GpuModel::VENDOR_NAME->value, $this->query); - parent::make_wildcard_search(GpuModel::VENDOR_MODEL->value, $this->query); - parent::make_wildcard_search(GpuModel::VENDOR_CHIP_NAME->value, $this->query); - parent::make_wildcard_search(GpuModel::VENDOR_CHIP_MODEL->value, $this->query); - - // Get hardware - $this->get_gpu(); - - // Resolve hardware relationships - $this->get_motherboards(); - - // Return 404 Not Found if response array is empty - return new Response($this->results, $this->results ? 200 : 404); - } - } \ No newline at end of file diff --git a/api/endpoints/messages/POST.php b/api/endpoints/messages/POST.php deleted file mode 100755 index f8761b4..0000000 --- a/api/endpoints/messages/POST.php +++ /dev/null @@ -1,51 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->POST([ - (new Rules(MessagesModel::EMAIL->value)) - ->type(Type::STRING) - ->max(255) - ->default(null), - - (new Rules(MessagesModel::MESSAGE->value)) - ->required() - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_TEXT_MAX_LENGTH) - ]); - - parent::__construct(Databases::VLW, $this->ruleset); - } - - public function main(): Response { - // Use copy of request body as entity - $entity = $_POST; - - $entity[MessagesModel::ID->value] = parent::gen_uuid4(); - $entity[MessagesModel::DATE_CREATED->value] = time(); - - return $this->db->for(MessagesModel::TABLE)->insert($entity) === true - ? new Response($entity[MessagesModel::ID->value], 201) - : new Response("Failed to create message", 500); - } - } \ No newline at end of file diff --git a/api/endpoints/search/GET.php b/api/endpoints/search/GET.php deleted file mode 100755 index 4454263..0000000 --- a/api/endpoints/search/GET.php +++ /dev/null @@ -1,59 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->GET([ - (new Rules(self::GET_QUERY)) - ->required() - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) - ]); - - parent::__construct(Databases::VLW, $this->ruleset); - } - - private function search_work(): Response { - return (new Call(Endpoints::WORK->value))->params([ - WorkModel::TITLE->value => $_GET[self::GET_QUERY], - WorkModel::SUMMARY->value => $_GET[self::GET_QUERY] - ])->get(); - } - - public function main(): Response { - $results = [ - Endpoints::WORK->value => $this->search_work()->output() - ]; - - // Calculate the total number of results from all searched endpoints - $num_results = array_sum(array_map(fn(array $result): int => count($result), array_values($results))); - - // Return 404 if no search results - return new Response($results, $num_results > 0 ? 200 : 404); - } - } \ No newline at end of file diff --git a/api/endpoints/work/DELETE.php b/api/endpoints/work/DELETE.php deleted file mode 100755 index 2b5905a..0000000 --- a/api/endpoints/work/DELETE.php +++ /dev/null @@ -1,64 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->POST([ - (new Rules(WorkModel::ID->value)) - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(WorkModel::TITLE->value)) - ->type(Type::STRING) - ->min(3) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(WorkModel::SUMMARY->value)) - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_TEXT_MAX_LENGTH), - - (new Rules(WorkModel::IS_LISTED->value)) - ->type(Type::BOOLEAN), - - (new Rules(WorkModel::DATE_MODIFIED->value)) - ->type(Type::NUMBER) - ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGTH), - - (new Rules(WorkModel::DATE_CREATED->value)) - ->type(Type::NUMBER) - ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGTH) - ]); - - parent::__construct(Databases::VLW, $this->ruleset); - } - - public function main(): Response { - return $this->db->for(FieldsEnumsModel::TABLE)->delete($_POST) === true - ? new Response(RESP_DELETE_OK) - : new Response("Failed to delete work entity", 500); - } - } \ No newline at end of file diff --git a/api/endpoints/work/GET.php b/api/endpoints/work/GET.php deleted file mode 100755 index 21f3046..0000000 --- a/api/endpoints/work/GET.php +++ /dev/null @@ -1,107 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->GET([ - (new Rules(WorkModel::ID->value)) - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(WorkModel::TITLE->value)) - ->type(Type::STRING) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(WorkModel::SUMMARY->value)) - ->type(Type::STRING) - ->max(parent::MYSQL_TEXT_MAX_LENGTH), - - (new Rules(WorkModel::IS_LISTED->value)) - ->type(Type::BOOLEAN) - ->default(true), - - (new Rules(WorkModel::DATE_MODIFIED->value)) - ->type(Type::NUMBER) - ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGTH), - - (new Rules(WorkModel::DATE_CREATED->value)) - ->type(Type::NUMBER) - ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGTH), - - (new Rules(PARAM_LIMIT)) - ->type(Type::NUMBER) - ->type(Type::NULL) - ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGTH) - ->default(null) - ]); - - parent::__construct(Databases::VLW, $this->ruleset); - } - - public function main(): Response { - // Use search parameters from model as filters - $filters = $_GET; - // Unset keys not included in database model from filter - foreach (array_diff(array_keys($_GET), WorkModel::values()) as $k) { - unset($filters[$k]); - } - - // Do a wildcard search on the title column if provided - if (array_key_exists(WorkModel::TITLE->value, $_GET)) { - $filters[WorkModel::TITLE->value] = [ - "LIKE" => "%{$_GET[WorkModel::TITLE->value]}%" - ]; - } - - // Do a wildcard search on the summary column if provided - if (array_key_exists(WorkModel::SUMMARY->value, $_GET)) { - $filters[WorkModel::SUMMARY->value] = [ - "LIKE" => "%{$_GET[WorkModel::SUMMARY->value]}%" - ]; - } - - $response = $this->db->for(WorkModel::TABLE) - ->where($filters) - ->order([WorkModel::DATE_CREATED->value => "DESC"]) - ->limit($_GET[PARAM_LIMIT]) - ->select([ - WorkModel::ID->value, - WorkModel::TITLE->value, - WorkModel::SUMMARY->value, - WorkModel::IS_LISTED->value, - WorkModel::DATE_YEAR->value, - WorkModel::DATE_MONTH->value, - WorkModel::DATE_DAY->value, - WorkModel::DATE_MODIFIED->value, - WorkModel::DATE_CREATED->value - ]); - - return $response->num_rows > 0 - ? new Response($response->fetch_all(MYSQLI_ASSOC)) - : new Response([], 404); - } - } \ No newline at end of file diff --git a/api/endpoints/work/PATCH.php b/api/endpoints/work/PATCH.php deleted file mode 100755 index de44730..0000000 --- a/api/endpoints/work/PATCH.php +++ /dev/null @@ -1,115 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->GET([ - (new Rules(WorkModel::ID->value)) - ->required() - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) - ]); - - $this->ruleset->POST([ - (new Rules(WorkModel::TITLE->value)) - ->type(Type::STRING) - ->min(3) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(WorkModel::SUMMARY->value)) - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_TEXT_MAX_LENGTH), - - (new Rules(WorkModel::IS_LISTED->value)) - ->type(Type::BOOLEAN), - - (new Rules(WorkModel::DATE_MODIFIED->value)) - ->type(Type::NUMBER) - ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGTH) - ->default(time()), - - (new Rules(WorkModel::DATE_CREATED->value)) - ->type(Type::NUMBER) - ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGTH) - ]); - - parent::__construct(); - } - - // Generate a slug URL from string - private static function gen_slug(string $input): string { - return strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $input))); - } - - // Compute and return modeled year, month, and day from Unix timestamp in request body - private static function gen_date_created(): array { - return [ - WorkModel::DATE_YEAR->value => date("Y", $_POST[WorkModel::DATE_CREATED->value]), - WorkModel::DATE_MONTH ->value => date("n", $_POST[WorkModel::DATE_CREATED->value]), - WorkModel::DATE_DAY->value => date("j", $_POST[WorkModel::DATE_CREATED->value]) - ]; - } - - private function get_entity_by_id(string $id): Response { - return (new Call(Endpoints::WORK->value))->params([ - WorkModel::ID->value => $id - ])->get(); - } - - public function main(): Response { - // Use copy of request body as entity - $entity = $_POST; - - // Generate a new slug id from title if changed - if ($_POST[WorkModel::TITLE->value]) { - $slug = $_POST[WorkModel::TITLE->value]; - - // Bail out if the slug generated from the new tite already exist - if ($this->get_entity_by_id($slug)) { - return new Response("An entity with this title already exist", 409); - } - - // Add the new slug to update entity - $entity[WorkModel::ID] = $slug; - } - - // Generate new work date fields from timestamp - if ($_POST[WorkModel::DATE_CREATED->value]) { - array_merge($entity, self::gen_date_created()); - } - - // Update entity by existing id - return $this->db->for(WorkModel::TABLE)->where([WorkModel::ID->value => $_GET[WorkModel::ID->value]])->update($entity) === true - ? new Response($_GET[WorkModel::ID->value]) - : new Response("Failed to update entity", 500); - } - } \ No newline at end of file diff --git a/api/endpoints/work/POST.php b/api/endpoints/work/POST.php deleted file mode 100755 index cf6c3c1..0000000 --- a/api/endpoints/work/POST.php +++ /dev/null @@ -1,110 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->POST([ - (new Rules(WorkModel::TITLE->value)) - ->type(Type::STRING) - ->min(3) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) - ->default(null), - - (new Rules(WorkModel::SUMMARY->value)) - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_TEXT_MAX_LENGTH) - ->default(null), - - (new Rules(WorkModel::IS_LISTED->value)) - ->type(Type::BOOLEAN) - ->default(false), - - (new Rules(WorkModel::DATE_CREATED->value)) - ->type(Type::NUMBER) - ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGTH) - ->default(time()) - ]); - - parent::__construct(Databases::VLW, $this->ruleset); - } - - // Generate a slug URL from string - private static function gen_slug(string $input): string { - return strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $input))); - } - - // Compute and return modeled year, month, and day from a Unix timestamp - private static function gen_date_created(): array { - // Use provided timestamp in request - $date_created = $_POST[WorkModel::DATE_CREATED->value]; - - return [ - WorkModel::DATE_YEAR->value => date("Y", $date_created), - WorkModel::DATE_MONTH ->value => date("n", $date_created), - WorkModel::DATE_DAY->value => date("j", $date_created) - ]; - } - - private function get_entity_by_id(string $id): Response { - return (new Call(Endpoints::WORK->value))->params([ - WorkModel::ID->value => $id - ])->get(); - } - - public function main(): Response { - // Use copy of request body as entity - $entity = $_POST; - - // Generate URL slug from title text or UUID if undefined - $entity[WorkModel::ID->value] = $_POST[WorkModel::TITLE->value] - ? self::gen_slug($_POST[WorkModel::TITLE->value]) - : parent::gen_uuid4(); - - // Bail out here if a work entry with id had been created already - if ($this->get_entity_by_id($entity[WorkModel::ID->value])->ok) { - return new Response("An entity with id '{$slug}' already exist", 409); - } - - // Generate the necessary date fields - array_merge($entity, self::gen_date_created()); - - // Let's try to insert the new entity - if (!$this->db->for(WorkModel::TABLE)->insert($entity)) { - return new Response("Failed to insert work entry", 500); - } - - // Generate permalink for new entity - return (new Call(Endpoints::WORK_PERMALINKS->value))->post([ - WorkPermalinksModel::ID => $entity[WorkModel::ID->value], - WorkPermalinksModel::REF_WORK_ID => $entity[WorkModel::ID->value], - WorkPermalinksModel::DATE_CREATED => time() - ]); - } - } \ No newline at end of file diff --git a/api/endpoints/work/actions/DELETE.php b/api/endpoints/work/actions/DELETE.php deleted file mode 100755 index a0d582c..0000000 --- a/api/endpoints/work/actions/DELETE.php +++ /dev/null @@ -1,38 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->POST([ - (new Rules(WorkActionsModel::REF_WORK_ID->value)) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) - ]); - } - - public function main(): Response { - return $this->db->for(WorkActionsModel::TABLE)->delete($_POST) === true - ? new Response(RESP_DELETE_OK) - : new Response("Failed to delete action for work entity", 500); - } - } \ No newline at end of file diff --git a/api/endpoints/work/actions/GET.php b/api/endpoints/work/actions/GET.php deleted file mode 100644 index 902a97d..0000000 --- a/api/endpoints/work/actions/GET.php +++ /dev/null @@ -1,48 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->GET([ - (new Rules(WorkActionsModel::REF_WORK_ID->value)) - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) - ]); - - parent::__construct(Databases::VLW, $this->ruleset); - } - - public function main(): Response { - $response = $this->db->for(WorkActionsModel::TABLE) - ->where($_GET) - ->select([ - WorkActionsModel::REF_WORK_ID->value, - WorkActionsModel::DISPLAY_TEXT->value, - WorkActionsModel::HREF->value, - WorkActionsModel::CLASS_LIST->value - ]); - - return $response->num_rows > 0 - ? new Response(parent::index_array_by_key($response->fetch_all(MYSQLI_ASSOC), WorkActionsModel::REF_WORK_ID->value)) - : new Response([], 404); - } - } \ No newline at end of file diff --git a/api/endpoints/work/actions/POST.php b/api/endpoints/work/actions/POST.php deleted file mode 100755 index 7c8175a..0000000 --- a/api/endpoints/work/actions/POST.php +++ /dev/null @@ -1,76 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->POST([ - (new Rules(WorkActionsModel::REF_WORK_ID->value)) - ->required() - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(WorkActionsModel::DISPLAY_TEXT->value)) - ->required() - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(WorkActionsModel::HREF->value)) - ->required() - ->type(Type::STRING) - ->type(Type::NULL) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(WorkActionsModel::CLASS_LIST->value)) - ->type(Type::ARRAY) - ->min(1) - ->default([]) - ]); - - parent::__construct(Databases::VLW, $this->ruleset); - } - - private static function get_entity(): Response { - return (new Call(Endpoints::WORK->value))->params([ - WorkModel::ID->value => $_POST[WorkActionsModel::REF_WORK_ID->value] - ])->get(); - } - - public function main(): Response { - // Bail out if work entity could not be fetched - $entity = self::get_entity(); - if (!$entity->ok) { - return $entity; - } - - return $this->db->for(WorkActionsModel::TABLE)->insert($_POST) === true - ? new Response($_POST[WorkActionsModel::REF_WORK_ID->value], 201) - : new Response("Failed to add action to work entity", 500); - } - } \ No newline at end of file diff --git a/api/endpoints/work/permalinks/GET.php b/api/endpoints/work/permalinks/GET.php deleted file mode 100755 index d414e14..0000000 --- a/api/endpoints/work/permalinks/GET.php +++ /dev/null @@ -1,52 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->GET([ - (new Rules(WorkPermalinksModel::ID->value)) - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(WorkPermalinksModel::REF_WORK_ID->value)) - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) - ]); - - parent::__construct(Databases::VLW, $this->ruleset); - } - - public function main(): Response { - $response = $this->db->for(WorkPermalinksModel::TABLE) - ->where($_GET) - ->select([ - WorkPermalinksModel::ID->value, - WorkPermalinksModel::REF_WORK_ID->value, - WorkPermalinksModel::DATE_CREATED->value - ]); - - return $response->num_rows > 0 - ? new Response($response->fetch_all(MYSQLI_ASSOC)) - : new Response([], 404); - } - } \ No newline at end of file diff --git a/api/endpoints/work/permalinks/POST.php b/api/endpoints/work/permalinks/POST.php deleted file mode 100755 index fd2d904..0000000 --- a/api/endpoints/work/permalinks/POST.php +++ /dev/null @@ -1,65 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->POST([ - (new Rules(WorkPermalinksModel::ID->value)) - ->required() - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(WorkPermalinksModel::REF_WORK_ID->value)) - ->required() - ->type(Type::STRING) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(WorkPermalinksModel::DATE_CREATED->value)) - ->type(Type::NUMBER) - ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGTH) - ->default(time()) - ]); - - parent::__construct(Databases::VLW, $this->ruleset); - } - - private static function get_entity(): Response { - return (new Call(Endpoints::WORK->value))->params([ - WorkModel::ID->value => $_POST[WorkTagsModel::REF_WORK_ID->value] - ])->get(); - } - - public function main(): Response { - // Bail out if work entity could not be fetched - $entity = self::get_entity(); - if (!$entity->ok) { - return $entity; - } - - return $this->db->for(WorkPermalinksModel::TABLE)->insert($_POST) === true - ? new Response($_POST[WorkPermalinksModel::ID->value], 201) - : new Response("Failed to add permalink to work entity", 500); - } - } \ No newline at end of file diff --git a/api/endpoints/work/tags/DELETE.php b/api/endpoints/work/tags/DELETE.php deleted file mode 100755 index 04c6924..0000000 --- a/api/endpoints/work/tags/DELETE.php +++ /dev/null @@ -1,42 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->GET([ - (new Rules(WorkTagsModel::REF_WORK_ID->value)) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(WorkTagsModel::NAME->value)) - ->type(Type::ENUM, WorkTagsNameEnum::names()) - ]); - - parent::__construct(Databases::VLW, $this->ruleset); - } - - public function main(): Response { - return $this->db->for(WorkTagsModel::TABLE)->delete($_POST) === true - ? new Response(RESP_DELETE_OK) - : new Response("Failed to delete value from document", 500); - } - } \ No newline at end of file diff --git a/api/endpoints/work/tags/GET.php b/api/endpoints/work/tags/GET.php deleted file mode 100644 index 9d2bae3..0000000 --- a/api/endpoints/work/tags/GET.php +++ /dev/null @@ -1,51 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->GET([ - (new Rules(WorkTagsModel::REF_WORK_ID->value)) - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(WorkTagsModel::NAME->value)) - ->type(Type::ENUM, WorkTagsNameEnum::names()) - ]); - - parent::__construct(Databases::VLW, $this->ruleset); - } - - public function main(): Response { - $response = $this->db->for(WorkTagsModel::TABLE) - ->where($_GET) - ->select([ - WorkTagsModel::REF_WORK_ID->value, - WorkTagsModel::NAME->value - ]); - - return $response->num_rows > 0 - ? new Response(parent::index_array_by_key($response->fetch_all(MYSQLI_ASSOC), WorkTagsModel::REF_WORK_ID->value)) - : new Response([], 404); - } - } \ No newline at end of file diff --git a/api/endpoints/work/tags/POST.php b/api/endpoints/work/tags/POST.php deleted file mode 100755 index e10ae90..0000000 --- a/api/endpoints/work/tags/POST.php +++ /dev/null @@ -1,63 +0,0 @@ -ruleset = new Ruleset(strict: true); - - $this->ruleset->POST([ - (new Rules(WorkTagsModel::REF_WORK_ID->value)) - ->required() - ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), - - (new Rules(WorkTagsModel::NAME->value)) - ->required() - ->type(Type::ENUM, WorkTagsNameEnum::names()) - ]); - - parent::__construct(Databases::VLW, $this->ruleset); - } - - private static function get_entity(): Response { - return (new Call(Endpoints::WORK->value))->params([ - WorkModel::ID->value => $_POST[WorkTagsModel::REF_WORK_ID->value] - ])->get(); - } - - public function main(): Response { - // Bail out if work entity could not be fetched - $entity = self::get_entity(); - if (!$entity->ok) { - return $entity; - } - - return $this->db->for(WorkTagsModel::TABLE)->insert($_POST) === true - ? new Response($_POST[WorkTagsModel::REF_WORK_ID->value], 201) - : new Response("Failed to add tag to work entity", 500); - } - } \ No newline at end of file diff --git a/api/install.sh b/api/install.sh deleted file mode 100644 index 0c7900a..0000000 --- a/api/install.sh +++ /dev/null @@ -1,2 +0,0 @@ -# Install dependencies -composer install --optimize-autoloader \ No newline at end of file diff --git a/api/src/databases/VLWdb.php b/api/src/databases/VLWdb.php deleted file mode 100755 index 7a06a4f..0000000 --- a/api/src/databases/VLWdb.php +++ /dev/null @@ -1,101 +0,0 @@ -db = new MySQL( - $_ENV["connect"]["host"], - $_ENV["connect"]["user"], - $_ENV["connect"]["pass"], - $_ENV["databases"][$database->value], - ); - } - - // Bail out if provided ReflectRules\Ruleset is invalid - private static function eval_ruleset_or_exit(Ruleset $ruleset): ?Response { - return !$ruleset->is_valid() ? new Response($ruleset->get_errors(), 422) : null; - } - - // Generate and return UUID4 string - public static function gen_uuid4(): string { - return sprintf("%04x%04x-%04x-%04x-%04x-%04x%04x%04x", - // 32 bits for "time_low" - mt_rand(0, 0xffff), mt_rand(0, 0xffff), - - // 16 bits for "time_mid" - mt_rand(0, 0xffff), - - // 16 bits for "time_hi_and_version", - // four most significant bits holds version number 4 - mt_rand(0, 0x0fff) | 0x4000, - - // 16 bits, 8 bits for "clk_seq_hi_res", - // 8 bits for "clk_seq_low", - // two most significant bits holds zero and one for variant DCE1.1 - mt_rand(0, 0x3fff) | 0x8000, - - // 48 bits for "node" - mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) - ); - } - - // Mutate the value by array key $property_name into a libmysqldriver\MySQL custom operator - // https://codeberg.org/vlw/php-mysql#define-custom-operators - public static function make_wildcard_search(string $property_name, array &$filters): array { - // Bail out if property name is not set in filters array or if its value is null - if (!array_key_exists($property_name, $filters) || $filters[$property_name] === null) { - return $filters; - } - - // Mutate filter value into a custom operator array - $filters[$property_name] = [ - "LIKE" => "%{$filters[$property_name]}%" - ]; - - return $filters; - } - - public function index_array_by_key(array $input, string $key): array { - $output = []; - - foreach ($input as $item) { - $idx = $item[$key]; - - // Create entry for key in output array if first item - if (!array_key_exists($idx, $output)) { - $output[$idx] = []; - } - - // Append item to array of array by key - $output[$idx][] = $item; - } - - return $output; - } - } \ No newline at end of file diff --git a/api/src/databases/models/Battlestation/Config/ChassisMb.php b/api/src/databases/models/Battlestation/Config/ChassisMb.php deleted file mode 100644 index 1d549d2..0000000 --- a/api/src/databases/models/Battlestation/Config/ChassisMb.php +++ /dev/null @@ -1,14 +0,0 @@ -ruleset = new Ruleset(strict: true); + $this->ruleset->validate_or_exit(); + + parent::__construct(); + } + + private function languages(): array { + $resp = (new Call(Endpoints::ABOUT_LANGUAGES->value))->get(); + + return array_column($resp->output(), LanguagesTable::ID->value); + } + + // Delete languages cache file if it exists + public function main(): Response { + $this->db->for(LanguagesTable::NAME); + + foreach ($this->languages() as $language){ + $this->db->delete([LanguagesTable::ID->value => $language]); + } + + return new Response(); + } + } \ No newline at end of file diff --git a/endpoints/about/languages/GET.php b/endpoints/about/languages/GET.php new file mode 100644 index 0000000..92e3b29 --- /dev/null +++ b/endpoints/about/languages/GET.php @@ -0,0 +1,54 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->GET([ + (new Rules(LanguagesTable::ID->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::SIZE_VARCHAR), + + (new Rules(LanguagesTable::BYTES->value)) + ->type(Type::NUMBER) + ->min(1) + ->max(parent::SIZE_UINT32), + + (new Rules(FORGEJO_UPDATE_CACHE_PARAM)) + ->type(Type::BOOLEAN) + ->default(false) + ]); + + $this->ruleset->validate_or_exit(); + + parent::__construct(); + } + + public function main(): Response { + // Refresh the language cache if param is set + if ($_GET[FORGEJO_UPDATE_CACHE_PARAM]) { + (new Call(Endpoints::ABOUT_LANGUAGES->value))->post(); + } + + return $this->list(LanguagesTable::NAME, LanguagesTable::values(), [ + LanguagesTable::BYTES->value => Order::DESC + ]); + } + } \ No newline at end of file diff --git a/api/endpoints/about/languages/POST.php b/endpoints/about/languages/POST.php similarity index 61% rename from api/endpoints/about/languages/POST.php rename to endpoints/about/languages/POST.php index 1b3eb0f..d75189b 100644 --- a/api/endpoints/about/languages/POST.php +++ b/endpoints/about/languages/POST.php @@ -1,20 +1,29 @@ ruleset = new Ruleset(strict: true); + $this->ruleset->validate_or_exit(); + + parent::__construct(); + } // Fetch JSON from URL private static function fetch_json(string $url): array { @@ -23,20 +32,23 @@ // Fetch JSON from a Forgejo endpoint private static function fetch_endpoint(string $endpoint): array { - $url = $_ENV["forgejo"]["base_url"] . $endpoint; + $url = $_ENV["server_forgejo"]["base_url"] . $endpoint; return self::fetch_json($url); } // Write $this->languages to a JSON file - private function cache_languages(): int|bool { - $cache_filename = $_ENV["forgejo_languages"]["cache_file"]; + private function cache_languages(): void { + // Delete existing cache + (new Call(Endpoints::ABOUT_LANGUAGES->value))->delete(); - // Bail out if cache file is not configured - if (empty($cache_filename)) { - return true; + $this->db->for(LanguagesTable::NAME); + + foreach ($this->languages as $language => $bytes) { + $this->db->insert([ + LanguagesTable::ID->value => $language, + LanguagesTable::BYTES->value => $bytes + ]); } - - return file_put_contents($cache_filename, json_encode($this->languages)); } // Fetch and add languages to total from a fully-qualified Forgejo URL @@ -71,13 +83,10 @@ // Add languages from all public repositories for profiles in config private function add_repositories_from_config_profiles(): void { - foreach(explode(",", $_ENV["forgejo_languages"]["scan_profiles"]) as $profile) { + foreach(explode(",", $_ENV["server_forgejo"]["scan_profiles"]) as $profile) { // Resolve user data from username $user = self::fetch_endpoint(sprintf(FORGEJO_ENDPOINT_USER, $profile)); - - if (!$this->add_public_repositores($user["id"])) { - $this->errors[] = $profile; - } + $this->add_public_repositores($user["id"]); } } @@ -87,11 +96,7 @@ // Sort langauges bytes tally by largest in descending order arsort($this->languages); - // Save languages to cache - if (!$this->cache_languages()) { - $this->errors[] = ERRNO_CACHE_FAILED; - } - + $this->cache_languages(); return new Response($this->languages); } } \ No newline at end of file diff --git a/api/endpoints/battlestation/GET.php b/endpoints/battlestation/GET.php similarity index 69% rename from api/endpoints/battlestation/GET.php rename to endpoints/battlestation/GET.php index a02b5cc..b238306 100644 --- a/api/endpoints/battlestation/GET.php +++ b/endpoints/battlestation/GET.php @@ -1,21 +1,15 @@ value)) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) + ->max(parent::SIZE_VARCHAR) ]); parent::__construct(Databases::BATTLESTATION, $this->ruleset); @@ -44,7 +38,7 @@ private function get_config(): array { return $this->results = $this->db - ->for(ConfigModel::TABLE) + ->for(ConfigModel::NAME) ->where($this->query) ->order([ConfigModel::DATE_BUILT->value => "DESC"]) ->select(ConfigModel::values()) diff --git a/api/endpoints/battlestation/chassis/GET.php b/endpoints/battlestation/chassis/GET.php similarity index 52% rename from api/endpoints/battlestation/chassis/GET.php rename to endpoints/battlestation/chassis/GET.php index b00c21d..4f8618a 100644 --- a/api/endpoints/battlestation/chassis/GET.php +++ b/endpoints/battlestation/chassis/GET.php @@ -1,23 +1,17 @@ ruleset = new Ruleset(strict: true); $this->ruleset->GET([ - (new Rules(ChassisModel::ID->value)) + (new Rules(ChassisTable::ID->value)) ->type(Type::STRING) ->min(parent::UUID_LENGTH) ->max(parent::UUID_LENGTH), - (new Rules(ChassisModel::VENDOR_NAME->value)) + (new Rules(ChassisTable::VENDOR_NAME->value)) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + ->max(parent::SIZE_VARCHAR), - (new Rules(ChassisModel::VENDOR_MODEL->value)) + (new Rules(ChassisTable::VENDOR_MODEL->value)) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + ->max(parent::SIZE_VARCHAR), - (new Rules(ChassisModel::STORAGE_TWOINCHFIVE->value)) + (new Rules(ChassisTable::STORAGE_TWOINCHFIVE->value)) ->type(Type::NUMBER) ->type(Type::NULL) ->min(0) ->max(parent::MYSQL_TINYINT_MAX_LENGTH), - (new Rules(ChassisModel::STORAGE_THREEINCHFIVE->value)) + (new Rules(ChassisTable::STORAGE_THREEINCHFIVE->value)) ->type(Type::NUMBER) ->type(Type::NULL) ->min(0) ->max(parent::MYSQL_TINYINT_MAX_LENGTH), - (new Rules(ChassisModel::IS_RETIRED->value)) + (new Rules(ChassisTable::IS_RETIRED->value)) ->type(Type::BOOLEAN) ]); @@ -70,26 +64,26 @@ foreach ($this->results as &$result) { // Get motherboard id from relationship by chassis id $result[self::REL_MOTHERBOARDS] = $this->db - ->for(ChassisMbModel::TABLE) - ->where([ChassisMbModel::REF_CHASSIS_ID->value => $result[ChassisModel::ID->value]]) - ->select(ChassisMbModel::values()) + ->for(ChassisMbTable::NAME) + ->where([ChassisMbTable::REF_CHASSIS_ID->value => $result[ChassisTable::ID->value]]) + ->select(ChassisMbTable::values()) ->fetch_all(MYSQLI_ASSOC); } } private function get_chassis(): array { return $this->results = $this->db - ->for(ChassisModel::TABLE) + ->for(ChassisTable::NAME) ->where($this->query) - ->order([ChassisModel::DATE_AQUIRED->value => "DESC"]) - ->select(ChassisModel::values()) + ->order([ChassisTable::DATE_AQUIRED->value => "DESC"]) + ->select(ChassisTable::values()) ->fetch_all(MYSQLI_ASSOC); } public function main(): Response { // Set properties as "searchable" - parent::make_wildcard_search(ChassisModel::VENDOR_NAME->value, $this->query); - parent::make_wildcard_search(ChassisModel::VENDOR_MODEL->value, $this->query); + parent::make_wildcard_search(ChassisTable::VENDOR_NAME->value, $this->query); + parent::make_wildcard_search(ChassisTable::VENDOR_MODEL->value, $this->query); // Get hardware $this->get_chassis(); diff --git a/api/endpoints/battlestation/coolers/GET.php b/endpoints/battlestation/coolers/GET.php similarity index 75% rename from api/endpoints/battlestation/coolers/GET.php rename to endpoints/battlestation/coolers/GET.php index 5d93726..79dbe5d 100644 --- a/api/endpoints/battlestation/coolers/GET.php +++ b/endpoints/battlestation/coolers/GET.php @@ -1,25 +1,19 @@ value)) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + ->max(parent::SIZE_VARCHAR), (new Rules(CoolerModel::VENDOR_MODEL->value)) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + ->max(parent::SIZE_VARCHAR), (new Rules(CoolerModel::IS_RETIRED->value)) ->type(Type::BOOLEAN) @@ -75,7 +69,7 @@ foreach ($this->results as &$result) { // Get motherboard id from relationship by chassis id $result[self::REL_MOTHERBOARDS] = $this->db - ->for(MbCoolerModel::TABLE) + ->for(MbCoolerModel::NAME) ->where([MbCoolerModel::REF_COOLER_ID->value => $result[CoolerModel::ID->value]]) ->select(MbCoolerModel::values()) ->fetch_all(MYSQLI_ASSOC); @@ -84,7 +78,7 @@ private function get_coolers(): array { return $this->results = $this->db - ->for(CoolerModel::TABLE) + ->for(CoolerModel::NAME) ->where($this->query) ->order([CoolerModel::DATE_AQUIRED->value => "DESC"]) ->select(CoolerModel::values()) diff --git a/api/endpoints/battlestation/cpu/GET.php b/endpoints/battlestation/cpu/GET.php similarity index 56% rename from api/endpoints/battlestation/cpu/GET.php rename to endpoints/battlestation/cpu/GET.php index 2bdfb69..6c98fa0 100644 --- a/api/endpoints/battlestation/cpu/GET.php +++ b/endpoints/battlestation/cpu/GET.php @@ -1,26 +1,20 @@ ruleset = new Ruleset(strict: true); $this->ruleset->GET([ - (new Rules(CpuModel::ID->value)) + (new Rules(CpuTable::ID->value)) ->type(Type::STRING) ->min(parent::UUID_LENGTH) ->max(parent::UUID_LENGTH), - (new Rules(CpuModel::CLOCK_BASE->value)) + (new Rules(CpuTable::CLOCK_BASE->value)) ->type(Type::NUMBER) ->min(1), - (new Rules(CpuModel::CLOCK_TURBO->value)) + (new Rules(CpuTable::CLOCK_TURBO->value)) ->type(Type::NUMBER) ->min(1), - (new Rules(CpuModel::CORE_COUNT_PERFORMANCE->value)) + (new Rules(CpuTable::CORE_COUNT_PERFORMANCE->value)) ->type(Type::NUMBER) ->min(1) ->max(parent::MYSQL_TINYINT_MAX_LENGTH), - (new Rules(CpuModel::CORE_COUNT_EFFICIENCY->value)) + (new Rules(CpuTable::CORE_COUNT_EFFICIENCY->value)) ->type(Type::NUMBER) ->min(1) ->max(parent::MYSQL_TINYINT_MAX_LENGTH), - (new Rules(CpuModel::CORE_THREADS->value)) + (new Rules(CpuTable::CORE_THREADS->value)) ->type(Type::NUMBER) ->min(1) ->max(parent::MYSQL_TINYINT_MAX_LENGTH), - (new Rules(CpuModel::VENDOR_NAME->value)) + (new Rules(CpuTable::VENDOR_NAME->value)) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + ->max(parent::SIZE_VARCHAR), - (new Rules(CpuModel::VENDOR_MODEL->value)) + (new Rules(CpuTable::VENDOR_MODEL->value)) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + ->max(parent::SIZE_VARCHAR), - (new Rules(CpuModel::IS_RETIRED->value)) + (new Rules(CpuTable::IS_RETIRED->value)) ->type(Type::BOOLEAN) ]); @@ -84,8 +78,8 @@ foreach ($this->results as &$result) { // Get motherboard id from relationship by chassis id $result[self::REL_MOTHERBOARDS] = $this->db - ->for(MbCpuCoolerModel::TABLE) - ->where([MbCpuCoolerModel::REF_CPU_ID->value => $result[CpuModel::ID->value]]) + ->for(MbCpuCoolerModel::NAME) + ->where([MbCpuCoolerModel::REF_CPU_ID->value => $result[CpuTable::ID->value]]) ->select(MbCpuCoolerModel::values()) ->fetch_all(MYSQLI_ASSOC); } @@ -93,17 +87,17 @@ private function get_cpu(): array { return $this->results = $this->db - ->for(CpuModel::TABLE) + ->for(CpuTable::NAME) ->where($this->query) - ->order([CpuModel::DATE_AQUIRED->value => "DESC"]) - ->select(CpuModel::values()) + ->order([CpuTable::DATE_AQUIRED->value => "DESC"]) + ->select(CpuTable::values()) ->fetch_all(MYSQLI_ASSOC); } public function main(): Response { // Set properties as "searchable" - parent::make_wildcard_search(CpuModel::VENDOR_NAME->value, $this->query); - parent::make_wildcard_search(CpuModel::VENDOR_MODEL->value, $this->query); + parent::make_wildcard_search(CpuTable::VENDOR_NAME->value, $this->query); + parent::make_wildcard_search(CpuTable::VENDOR_MODEL->value, $this->query); // Get hardware $this->get_cpu(); diff --git a/api/endpoints/battlestation/dram/GET.php b/endpoints/battlestation/dram/GET.php similarity index 51% rename from api/endpoints/battlestation/dram/GET.php rename to endpoints/battlestation/dram/GET.php index 72539af..a847e63 100644 --- a/api/endpoints/battlestation/dram/GET.php +++ b/endpoints/battlestation/dram/GET.php @@ -1,27 +1,21 @@ ruleset = new Ruleset(strict: true); $this->ruleset->GET([ - (new Rules(DramModel::ID->value)) + (new Rules(DramTable::ID->value)) ->type(Type::STRING) ->min(parent::UUID_LENGTH) ->max(parent::UUID_LENGTH), - (new Rules(DramModel::CAPACITY->value)) + (new Rules(DramTable::CAPACITY->value)) ->type(Type::NUMBER) ->min(1), - (new Rules(DramModel::SPEED->value)) + (new Rules(DramTable::SPEED->value)) ->type(Type::NUMBER) ->min(1), - (new Rules(DramModel::FORMFACTOR->value)) + (new Rules(DramTable::FORMFACTOR->value)) ->type(Type::ENUM, DramFormfactorEnum::names()), - (new Rules(DramModel::TECHNOLOGY->value)) + (new Rules(DramTable::TECHNOLOGY->value)) ->type(Type::ENUM, DramTechnologyEnum::names()), - (new Rules(DramModel::ECC->value)) + (new Rules(DramTable::ECC->value)) ->type(Type::BOOLEAN), - (new Rules(DramModel::BUFFERED->value)) + (new Rules(DramTable::BUFFERED->value)) ->type(Type::BOOLEAN), - (new Rules(DramModel::VENDOR_NAME->value)) + (new Rules(DramTable::VENDOR_NAME->value)) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + ->max(parent::SIZE_VARCHAR), - (new Rules(DramModel::VENDOR_MODEL->value)) + (new Rules(DramTable::VENDOR_MODEL->value)) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + ->max(parent::SIZE_VARCHAR), - (new Rules(DramModel::IS_RETIRED->value)) + (new Rules(DramTable::IS_RETIRED->value)) ->type(Type::BOOLEAN) ]); @@ -82,26 +76,26 @@ foreach ($this->results as &$result) { // Get motherboard id from relationship by chassis id $result[self::REL_MOTHERBOARDS] = $this->db - ->for(MbDramModel::TABLE) - ->where([MbDramModel::REF_DRAM_ID->value => $result[DramModel::ID->value]]) - ->select(MbDramModel::values()) + ->for(MbDramTable::NAME) + ->where([MbDramTable::REF_DRAM_ID->value => $result[DramTable::ID->value]]) + ->select(MbDramTable::values()) ->fetch_all(MYSQLI_ASSOC); } } private function get_dram(): array { return $this->results = $this->db - ->for(DramModel::TABLE) + ->for(DramTable::NAME) ->where($this->query) - ->order([DramModel::DATE_AQUIRED->value => "DESC"]) - ->select(DramModel::values()) + ->order([DramTable::DATE_AQUIRED->value => "DESC"]) + ->select(DramTable::values()) ->fetch_all(MYSQLI_ASSOC); } public function main(): Response { // Set properties as "searchable" - parent::make_wildcard_search(DramModel::VENDOR_NAME->value, $this->query); - parent::make_wildcard_search(DramModel::VENDOR_MODEL->value, $this->query); + parent::make_wildcard_search(DramTable::VENDOR_NAME->value, $this->query); + parent::make_wildcard_search(DramTable::VENDOR_MODEL->value, $this->query); // Get hardware $this->get_dram(); diff --git a/endpoints/battlestation/gpu/GET.php b/endpoints/battlestation/gpu/GET.php new file mode 100644 index 0000000..f203c9f --- /dev/null +++ b/endpoints/battlestation/gpu/GET.php @@ -0,0 +1,101 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->GET([ + (new Rules(GpuTable::ID->value)) + ->type(Type::STRING) + ->min(parent::UUID_LENGTH) + ->max(parent::UUID_LENGTH), + + (new Rules(GpuTable::MEMORY->value)) + ->type(Type::NUMBER) + ->min(1), + + (new Rules(GpuTable::VENDOR_NAME->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::SIZE_VARCHAR), + + (new Rules(GpuTable::VENDOR_MODEL->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::SIZE_VARCHAR), + + (new Rules(GpuTable::VENDOR_CHIP_NAME->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::SIZE_VARCHAR), + + (new Rules(GpuTable::VENDOR_CHIP_MODEL->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::SIZE_VARCHAR), + + (new Rules(GpuTable::IS_RETIRED->value)) + ->type(Type::BOOLEAN) + ]); + + parent::__construct(Databases::BATTLESTATION, $this->ruleset); + + // Use a copy of search parameters + $this->query = $_GET; + } + + private function get_motherboards(): void { + foreach ($this->results as &$result) { + // Get motherboard id from relationship by chassis id + $result[self::REL_MOTHERBOARDS] = $this->db + ->for(MbGpuTable::NAME) + ->where([MbGpuTable::REF_GPU_ID->value => $result[GpuTable::ID->value]]) + ->select(MbGpuTable::values()) + ->fetch_all(MYSQLI_ASSOC); + } + } + + private function get_gpu(): array { + return $this->results = $this->db + ->for(GpuTable::NAME) + ->where($this->query) + ->order([GpuTable::DATE_AQUIRED->value => "DESC"]) + ->select(GpuTable::values()) + ->fetch_all(MYSQLI_ASSOC); + } + + public function main(): Response { + // Set properties as "searchable" + parent::make_wildcard_search(GpuTable::VENDOR_NAME->value, $this->query); + parent::make_wildcard_search(GpuTable::VENDOR_MODEL->value, $this->query); + parent::make_wildcard_search(GpuTable::VENDOR_CHIP_NAME->value, $this->query); + parent::make_wildcard_search(GpuTable::VENDOR_CHIP_MODEL->value, $this->query); + + // Get hardware + $this->get_gpu(); + + // Resolve hardware relationships + $this->get_motherboards(); + + // Return 404 Not Found if response array is empty + return new Response($this->results, $this->results ? 200 : 404); + } + } \ No newline at end of file diff --git a/api/endpoints/battlestation/mb/GET.php b/endpoints/battlestation/mb/GET.php similarity index 53% rename from api/endpoints/battlestation/mb/GET.php rename to endpoints/battlestation/mb/GET.php index d2c0ea0..d2a41a0 100644 --- a/api/endpoints/battlestation/mb/GET.php +++ b/endpoints/battlestation/mb/GET.php @@ -1,38 +1,32 @@ ruleset = new Ruleset(strict: true); $this->ruleset->GET([ - (new Rules(MbModel::ID->value)) + (new Rules(MbTable::ID->value)) ->type(Type::STRING) ->min(parent::UUID_LENGTH) ->max(parent::UUID_LENGTH), - (new Rules(MbModel::FORMFACTOR->value)) + (new Rules(MbTable::FORMFACTOR->value)) ->type(Type::ENUM, MbFormfactorEnum::names()), - (new Rules(MbModel::VENDOR_NAME->value)) + (new Rules(MbTable::VENDOR_NAME->value)) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + ->max(parent::SIZE_VARCHAR), - (new Rules(MbModel::VENDOR_MODEL->value)) + (new Rules(MbTable::VENDOR_MODEL->value)) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + ->max(parent::SIZE_VARCHAR), - (new Rules(MbModel::NETWORK_ETHERNET->value)) + (new Rules(MbTable::NETWORK_ETHERNET->value)) ->type(Type::NULL) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + ->max(parent::SIZE_VARCHAR), - (new Rules(MbModel::NETWORK_WLAN->value)) + (new Rules(MbTable::NETWORK_WLAN->value)) ->type(Type::NULL) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + ->max(parent::SIZE_VARCHAR), - (new Rules(MbModel::NETWORK_BLUETOOTH->value)) + (new Rules(MbTable::NETWORK_BLUETOOTH->value)) ->type(Type::NULL) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + ->max(parent::SIZE_VARCHAR), - (new Rules(MbModel::IS_RETIRED->value)) + (new Rules(MbTable::IS_RETIRED->value)) ->type(Type::BOOLEAN) ]); @@ -99,9 +93,9 @@ foreach ($this->results as &$result) { // Get motherboard id from relationship by chassis id $result[self::REL_CHASSIS] = $this->db - ->for(ChassisMbModel::TABLE) - ->where([ChassisMbModel::REF_MB_ID->value => $result[MbModel::ID->value]]) - ->select(ChassisMbModel::values()) + ->for(ChassisMbTable::NAME) + ->where([ChassisMbTable::REF_MB_ID->value => $result[MbTable::ID->value]]) + ->select(ChassisMbTable::values()) ->fetch_all(MYSQLI_ASSOC); } } @@ -110,9 +104,9 @@ foreach ($this->results as &$result) { // Get motherboard id from relationship by chassis id $result[self::REL_PSU] = $this->db - ->for(MbPsuModel::TABLE) - ->where([MbPsuModel::REF_MB_ID->value => $result[MbModel::ID->value]]) - ->select(MbPsuModel::values()) + ->for(MbPsuTable::NAME) + ->where([MbPsuTable::REF_MB_ID->value => $result[MbTable::ID->value]]) + ->select(MbPsuTable::values()) ->fetch_all(MYSQLI_ASSOC); } } @@ -121,8 +115,8 @@ foreach ($this->results as &$result) { // Get motherboard id from relationship by chassis id $result[self::REL_CPU] = $this->db - ->for(MbCpuCoolerModel::TABLE) - ->where([MbCpuCoolerModel::REF_MB_ID->value => $result[MbModel::ID->value]]) + ->for(MbCpuCoolerModel::NAME) + ->where([MbCpuCoolerModel::REF_MB_ID->value => $result[MbTable::ID->value]]) ->select(MbCpuCoolerModel::values()) ->fetch_all(MYSQLI_ASSOC); } @@ -132,9 +126,9 @@ foreach ($this->results as &$result) { // Get motherboard id from relationship by chassis id $result[self::REL_GPU] = $this->db - ->for(MbGpuModel::TABLE) - ->where([MbGpuModel::REF_MB_ID->value => $result[MbModel::ID->value]]) - ->select(MbGpuModel::values()) + ->for(MbGpuTable::NAME) + ->where([MbGpuTable::REF_MB_ID->value => $result[MbTable::ID->value]]) + ->select(MbGpuTable::values()) ->fetch_all(MYSQLI_ASSOC); } } @@ -143,9 +137,9 @@ foreach ($this->results as &$result) { // Get motherboard id from relationship by chassis id $result[self::REL_DRAM] = $this->db - ->for(MbDramModel::TABLE) - ->where([MbDramModel::REF_MB_ID->value => $result[MbModel::ID->value]]) - ->select(MbDramModel::values()) + ->for(MbDramTable::NAME) + ->where([MbDramTable::REF_MB_ID->value => $result[MbTable::ID->value]]) + ->select(MbDramTable::values()) ->fetch_all(MYSQLI_ASSOC); } } @@ -154,9 +148,9 @@ foreach ($this->results as &$result) { // Get motherboard id from relationship by chassis id $result[self::REL_STORAGE] = $this->db - ->for(MbStorageModel::TABLE) - ->where([MbStorageModel::REF_MB_ID->value => $result[MbModel::ID->value]]) - ->select(MbStorageModel::values()) + ->for(MbStorageTable::NAME) + ->where([MbStorageTable::REF_MB_ID->value => $result[MbTable::ID->value]]) + ->select(MbStorageTable::values()) ->fetch_all(MYSQLI_ASSOC); } } @@ -165,17 +159,17 @@ private function get_motherboards(): array { return $this->results = $this->db - ->for(MbModel::TABLE) + ->for(MbTable::NAME) ->where($this->query) - ->order([MbModel::DATE_AQUIRED->value => "DESC"]) - ->select(MbModel::values()) + ->order([MbTable::DATE_AQUIRED->value => "DESC"]) + ->select(MbTable::values()) ->fetch_all(MYSQLI_ASSOC); } public function main(): Response { // Set properties as "searchable" - parent::make_wildcard_search(MbModel::VENDOR_NAME->value, $this->query); - parent::make_wildcard_search(MbModel::VENDOR_MODEL->value, $this->query); + parent::make_wildcard_search(MbTable::VENDOR_NAME->value, $this->query); + parent::make_wildcard_search(MbTable::VENDOR_MODEL->value, $this->query); // Get hardware $this->get_motherboards(); diff --git a/api/endpoints/battlestation/psu/GET.php b/endpoints/battlestation/psu/GET.php similarity index 52% rename from api/endpoints/battlestation/psu/GET.php rename to endpoints/battlestation/psu/GET.php index 2420422..f988b20 100644 --- a/api/endpoints/battlestation/psu/GET.php +++ b/endpoints/battlestation/psu/GET.php @@ -1,26 +1,20 @@ ruleset = new Ruleset(strict: true); $this->ruleset->GET([ - (new Rules(PsuModel::ID->value)) + (new Rules(PsuTable::ID->value)) ->type(Type::STRING) ->min(parent::UUID_LENGTH) ->max(parent::UUID_LENGTH), - (new Rules(PsuModel::POWER->value)) + (new Rules(PsuTable::POWER->value)) ->type(Type::NUMBER) ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGTH), + ->max(parent::SIZE_UINT8), - (new Rules(PsuModel::EIGHTYPLUS_RATING->value)) + (new Rules(PsuTable::EIGHTYPLUS_RATING->value)) ->type(Type::ENUM, EightyplusRatingEnum::names()), - (new Rules(PsuModel::VENDOR_NAME->value)) + (new Rules(PsuTable::VENDOR_NAME->value)) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + ->max(parent::SIZE_VARCHAR), - (new Rules(PsuModel::VENDOR_MODEL->value)) + (new Rules(PsuTable::VENDOR_MODEL->value)) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + ->max(parent::SIZE_VARCHAR), - (new Rules(PsuModel::IS_RETIRED->value)) + (new Rules(PsuTable::IS_RETIRED->value)) ->type(Type::BOOLEAN) ]); @@ -69,26 +63,26 @@ foreach ($this->results as &$result) { // Get motherboard id from relationship by chassis id $result[self::REL_MOTHERBOARDS] = $this->db - ->for(MbPsuModel::TABLE) - ->where([MbPsuModel::REF_PSU_ID->value => $result[PsuModel::ID->value]]) - ->select(MbPsuModel::values()) + ->for(MbPsuTable::NAME) + ->where([MbPsuTable::REF_PSU_ID->value => $result[PsuTable::ID->value]]) + ->select(MbPsuTable::values()) ->fetch_all(MYSQLI_ASSOC); } } private function get_psu(): array { return $this->results = $this->db - ->for(PsuModel::TABLE) + ->for(PsuTable::NAME) ->where($this->query) - ->order([PsuModel::DATE_AQUIRED->value => "DESC"]) - ->select(PsuModel::values()) + ->order([PsuTable::DATE_AQUIRED->value => "DESC"]) + ->select(PsuTable::values()) ->fetch_all(MYSQLI_ASSOC); } public function main(): Response { // Set properties as "searchable" - parent::make_wildcard_search(PsuModel::VENDOR_NAME->value, $this->query); - parent::make_wildcard_search(PsuModel::VENDOR_MODEL->value, $this->query); + parent::make_wildcard_search(PsuTable::VENDOR_NAME->value, $this->query); + parent::make_wildcard_search(PsuTable::VENDOR_MODEL->value, $this->query); // Get hardware $this->get_psu(); diff --git a/api/endpoints/battlestation/storage/GET.php b/endpoints/battlestation/storage/GET.php similarity index 51% rename from api/endpoints/battlestation/storage/GET.php rename to endpoints/battlestation/storage/GET.php index 4bfff74..4425788 100644 --- a/api/endpoints/battlestation/storage/GET.php +++ b/endpoints/battlestation/storage/GET.php @@ -1,28 +1,22 @@ ruleset = new Ruleset(strict: true); $this->ruleset->GET([ - (new Rules(StorageModel::ID->value)) + (new Rules(StorageTable::ID->value)) ->type(Type::STRING) ->min(parent::UUID_LENGTH) ->max(parent::UUID_LENGTH), - (new Rules(StorageModel::DISK_TYPE->value)) + (new Rules(StorageTable::DISK_TYPE->value)) ->type(Type::ENUM, StorageDiskTypeEnum::names()), - (new Rules(StorageModel::DISK_SIZE->value)) + (new Rules(StorageTable::DISK_SIZE->value)) ->type(Type::NUMBER) ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGTH), + ->max(parent::SIZE_UINT8), - (new Rules(StorageModel::DISK_INTERFACE->value)) + (new Rules(StorageTable::DISK_INTERFACE->value)) ->type(Type::ENUM, StorageDiskInterfaceEnum::names()), - (new Rules(StorageModel::DISK_FORMFACTOR->value)) + (new Rules(StorageTable::DISK_FORMFACTOR->value)) ->type(Type::ENUM, StorageDiskFormfactorEnum::names()), - (new Rules(StorageModel::VENDOR_NAME->value)) + (new Rules(StorageTable::VENDOR_NAME->value)) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + ->max(parent::SIZE_VARCHAR), - (new Rules(StorageModel::VENDOR_MODEL->value)) + (new Rules(StorageTable::VENDOR_MODEL->value)) ->type(Type::STRING) ->min(1) - ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + ->max(parent::SIZE_VARCHAR), - (new Rules(StorageModel::IS_RETIRED->value)) + (new Rules(StorageTable::IS_RETIRED->value)) ->type(Type::BOOLEAN) ]); @@ -77,26 +71,26 @@ foreach ($this->results as &$result) { // Get motherboard id from relationship by chassis id $result[self::REL_MOTHERBOARDS] = $this->db - ->for(MbStorageModel::TABLE) - ->where([MbStorageModel::REF_STORAGE_ID->value => $result[StorageModel::ID->value]]) - ->select(MbStorageModel::values()) + ->for(MbStorageTable::NAME) + ->where([MbStorageTable::REF_STORAGE_ID->value => $result[StorageTable::ID->value]]) + ->select(MbStorageTable::values()) ->fetch_all(MYSQLI_ASSOC); } } private function get_storage(): array { return $this->results = $this->db - ->for(StorageModel::TABLE) + ->for(StorageTable::NAME) ->where($this->query) - ->order([StorageModel::DATE_AQUIRED->value => "DESC"]) - ->select(StorageModel::values()) + ->order([StorageTable::DATE_AQUIRED->value => "DESC"]) + ->select(StorageTable::values()) ->fetch_all(MYSQLI_ASSOC); } public function main(): Response { // Set properties as "searchable" - parent::make_wildcard_search(StorageModel::VENDOR_NAME->value, $this->query); - parent::make_wildcard_search(StorageModel::VENDOR_MODEL->value, $this->query); + parent::make_wildcard_search(StorageTable::VENDOR_NAME->value, $this->query); + parent::make_wildcard_search(StorageTable::VENDOR_MODEL->value, $this->query); // Get hardware $this->get_storage(); diff --git a/endpoints/messages/POST.php b/endpoints/messages/POST.php new file mode 100644 index 0000000..9512460 --- /dev/null +++ b/endpoints/messages/POST.php @@ -0,0 +1,43 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->POST([ + (new Rules(MessagesTable::EMAIL->value)) + ->type(Type::STRING) + ->max(255) + ->default(null), + + (new Rules(MessagesTable::MESSAGE->value)) + ->required() + ->type(Type::STRING) + ->min(1) + ->max(parent::SIZE_TEXT) + ]); + + $this->ruleset->validate_or_exit(); + + parent::__construct(); + } + + public function main(): Response { + $_POST[MessagesTable::TIMESTAMP_CREATED->value] = time(); + + return $this->db->for(MessagesTable::NAME)->insert($_POST) === true + ? new Response(null, 201) + : new Response("Failed to send message", 500); + } + } \ No newline at end of file diff --git a/endpoints/search/DELETE.php b/endpoints/search/DELETE.php new file mode 100644 index 0000000..9dc485a --- /dev/null +++ b/endpoints/search/DELETE.php @@ -0,0 +1,35 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->POST([ + (new Rules(SearchTable::ID->value)) + ->required() + ->type(Type::STRING) + ->min(2) + ->max(parent::SIZE_VARCHAR) + ]); + + $this->ruleset->validate_or_exit(); + + parent::__construct(); + } + + public function main(): Response { + return $this->db->for(SearchTable::NAME)->delete($_POST) === true ? new Response() : new Response("", 500); + } + } \ No newline at end of file diff --git a/endpoints/search/GET.php b/endpoints/search/GET.php new file mode 100644 index 0000000..b89e952 --- /dev/null +++ b/endpoints/search/GET.php @@ -0,0 +1,96 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->GET([ + (new Rules(SearchTable::QUERY->value)) + ->type(Type::STRING) + ->min(2) + ->max(parent::SIZE_VARCHAR) + ->default(null), + + (new Rules(SearchTable::ID->value)) + ->type(Type::STRING) + ->min(1) + ->max(10) + ->default(null), + + (new Rules(SearchTable::CATEGORY->value)) + ->type(Type::ENUM, SearchCategoryEnum::names()) + ->default(null), + + (new Rules(SEARCH_UPDATE_CACHE_PARM)) + ->type(Type::BOOLEAN) + ->default(false) + ]); + + $this->ruleset->validate_or_exit(); + + parent::__construct(); + } + + private static function get_query(): string { + preg_match_all("/[a-zA-Z0-9]+/", $_GET[SearchTable::QUERY->value], $matches); + + return strtolower(implode("", $matches[0])); + } + + public function main(): Response { + // Freshen cache if update flag is set + if ($_GET[SEARCH_UPDATE_CACHE_PARM]) { + (new Call(Endpoints::SEARCH->value))->post(); + } + + $result = $this->db->for(SearchTable::NAME); + + if ($_GET[SearchTable::ID->value]) { + $result = $result->where([SearchTable::ID->value => $_GET[SearchTable::ID->value]]); + } else if ($_GET[SearchTable::QUERY->value]) { + $query = self::get_query(); + + $filter = [ + SearchTable::QUERY->value => [ + Operators::LIKE->value => "%{$query}%" + ] + ]; + + if ($_GET[SearchTable::CATEGORY->value]) { + $filter[SearchTable::CATEGORY->value] = $_GET[SearchTable::CATEGORY->value]; + } + + $result = $result->where($filter); + } else { + new Response([], 400); + } + + $result = $result->select([ + SearchTable::ID->value, + SearchTable::TITLE->value, + SearchTable::SUMMARY->value, + SearchTable::CATEGORY->value, + SearchTable::HREF->value + ]); + + return $result->num_rows > 0 + ? new Response($result->fetch_all(MYSQLI_ASSOC)) + : new Response([], 404); + } + } \ No newline at end of file diff --git a/endpoints/search/POST.php b/endpoints/search/POST.php new file mode 100644 index 0000000..107d627 --- /dev/null +++ b/endpoints/search/POST.php @@ -0,0 +1,73 @@ +ruleset = new Ruleset(strict: true); + $this->ruleset->validate_or_exit(); + + parent::__construct(); + } + + private static function truncate_string(string $input): string { + return substr($input, 0, SEARCH_QUERY_MAX_LENGTH); + } + + private static function create_query(string $input): string { + preg_match_all("/[a-zA-Z0-9]+/", $input, $matches); + + return self::truncate_string(strtolower(implode("", $matches[0]))); + } + + private function index_work(): void { + foreach ((new Call(Endpoints::WORK->value))->get()->output() as $result) { + $query = self::create_query(implode("", array_values($result)), 0, SEARCH_QUERY_MAX_LENGTH); + + // Get actions related to current result + $actions = (new Call(Endpoints::WORK_ACTIONS->value))->params([ + ActionsTable::REF_WORK_ID->value => $result[WorkTable::ID->value] + ])->get()->output(); + + $this->db->for(SearchTable::NAME)->insert([ + SearchTable::QUERY->value => $query, + SearchTable::ID->value => crc32($query), + SearchTable::TITLE->value => self::truncate_string($result[WorkTable::TITLE->value]), + SearchTable::SUMMARY->value => self::truncate_string($result[WorkTable::SUMMARY->value]), + SearchTable::CATEGORY->value => SearchCategoryEnum::WORK->name, + // Use first action as link for search result + SearchTable::HREF->value => $actions + ? self::truncate_string($actions[0][ActionsTable::HREF->value]) + : null + ]); + } + } + + public function main(): Response { + // Truncate existing cache + (new Call(Endpoints::SEARCH->value))->delete(); + + $this->index_work(); + + return $result->num_rows > 0 + ? new Response($result->fetch_all(MYSQLI_ASSOC)) + : new Response([], 404); + } + } \ No newline at end of file diff --git a/endpoints/work/GET.php b/endpoints/work/GET.php new file mode 100644 index 0000000..548a67d --- /dev/null +++ b/endpoints/work/GET.php @@ -0,0 +1,49 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->GET([ + (new Rules(WorkTable::ID->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::SIZE_VARCHAR), + + (new Rules(WorkTable::TITLE->value)) + ->type(Type::STRING) + ->max(parent::SIZE_VARCHAR), + + (new Rules(WorkTable::SUMMARY->value)) + ->type(Type::STRING) + ->max(parent::SIZE_TEXT), + + (new Rules(WorkTable::CREATED->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::SIZE_VARCHAR) + ]); + + $this->ruleset->validate_or_exit(); + + parent::__construct(); + } + + public function main(): Response { + return $this->list(WorkTable::NAME, WorkTable::values(), [ + WorkTable::CREATED->value => Order::DESC + ]); + } + } \ No newline at end of file diff --git a/endpoints/work/actions/GET.php b/endpoints/work/actions/GET.php new file mode 100644 index 0000000..8fa5e39 --- /dev/null +++ b/endpoints/work/actions/GET.php @@ -0,0 +1,36 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->GET([ + (new Rules(ActionsTable::REF_WORK_ID->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::SIZE_VARCHAR) + ]); + + $this->ruleset->validate_or_exit(); + + parent::__construct(); + } + + public function main(): Response { + return $this->list(ActionsTable::NAME, ActionsTable::values(), [ + ActionsTable::ORDER_IDX->value => Order::DESC + ]); + } + } \ No newline at end of file diff --git a/endpoints/work/tags/GET.php b/endpoints/work/tags/GET.php new file mode 100644 index 0000000..7d6ff52 --- /dev/null +++ b/endpoints/work/tags/GET.php @@ -0,0 +1,35 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->GET([ + (new Rules(TagsTable::REF_WORK_ID->value)) + ->min(1) + ->max(parent::SIZE_VARCHAR), + + (new Rules(TagsTable::LABEL->value)) + ->type(Type::ENUM, TagsLabelEnum::names()) + ]); + + $this->ruleset->validate_or_exit(); + + parent::__construct(); + } + + public function main(): Response { + return $this->list(TagsTable::NAME, TagsTable::values()); + } + } \ No newline at end of file diff --git a/endpoints/work/timeline/GET.php b/endpoints/work/timeline/GET.php new file mode 100644 index 0000000..2a5af72 --- /dev/null +++ b/endpoints/work/timeline/GET.php @@ -0,0 +1,53 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->GET([ + (new Rules(TimelineTable::REF_WORK_ID->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::SIZE_VARCHAR), + + (new Rules(TimelineTable::YEAR->value)) + ->type(Type::NUMBER) + ->min(0) + ->max(parent::SIZE_UINT16), + + (new Rules(TimelineTable::MONTH->value)) + ->type(Type::NUMBER) + ->min(0) + ->max(parent::SIZE_UINT8), + + (new Rules(TimelineTable::DAY->value)) + ->type(Type::NUMBER) + ->min(0) + ->max(parent::SIZE_UINT8) + ]); + + $this->ruleset->validate_or_exit(); + + parent::__construct(); + } + + public function main(): Response { + return $this->list(TimelineTable::NAME, TimelineTable::values(), [ + TimelineTable::YEAR->value => Order::DESC, + TimelineTable::MONTH->value => Order::DESC, + TimelineTable::DAY->value => Order::DESC + ]); + } + } \ No newline at end of file diff --git a/public/about.php b/public/about.php index 4851da7..918cf22 100644 --- a/public/about.php +++ b/public/about.php @@ -1,64 +1,39 @@ resp = $this->call(Endpoints::ABOUT_LANGUAGES->value)->get(); - - // We got a response from endpoint - if ($this->resp->ok) { - $this->languages = $this->resp->json(); - $this->total_bytes = array_sum($this->languages); - } + $this->total_bytes = array_sum(array_map(fn(Language $language): int => $language->bytes(), parent::all())); } - // Return all languages as (string) language => (int) language_bytes - public function all(): array { - return $this->languages; + public function percent(Language $language, int $mode = PHP_ROUND_HALF_UP): int { + return round(($language->bytes() / $this->total_bytes) * 100, 0, $mode); } - // Return percent of total for all languages - public function get_percent(string $lang, int $mode = PHP_ROUND_HALF_UP): int { - return round(($this->languages[$lang] / $this->total_bytes) * 100, 0, $mode); + public function percent_string(Language $language): string { + return ($this->percent($language) > 1 ? $this->percent($language) : "<1") . "%"; } - // Return language bytes as percent of whole - public function get_percent_str(string $lang): string { - $percent = $this->get_percent($lang, PHP_ROUND_HALF_DOWN); - return ($percent > 1 ? $percent : "<1") . "%"; - } - - // Return languages bytes as a multiple-byte decimal unit - public function get_bytes_str(string $lang): string { - $bytes = $this->languages[$lang]; - + public function bytes_si_string(Language $language): string { // Calculate factor for unit - $factor = floor((strlen($bytes) - 1) / 3); + $factor = floor((strlen($language->bytes()) - 1) / 3); // Divide by radix 10 - $format = $bytes / pow(1000, $factor); + $format = $language->bytes() / pow(1000, $factor); - return round($format) . " " . BYTE_UNITS[$factor]; + return round($format) . " " . FORGEJO_SI_BYTE_MULTIPLE[$factor]; } - }; + } ?> @@ -74,32 +49,30 @@
- all() as $lang => $bytes): ?> - - get_percent_str($lang) ?>
( bytes)
- + +
+ percent_string($language) ?> id ?>
(bytes() ?> bytes)
- all() as $lang => $bytes): ?> - - all() as $lang => $bytes): ?> - - get_percent_str($lang) ?>
( bytes)
- + +
+ percent_string($language) ?> id ?>
(bytes() ?> bytes)
diff --git a/public/about/battlestation.php b/public/about/battlestation.php index 21a753f..26dcba0 100644 --- a/public/about/battlestation.php +++ b/public/about/battlestation.php @@ -5,22 +5,22 @@ use VLW\Client\API; use VLW\API\Endpoints; - use VLW\API\Databases\VLWdb\Models\Battlestation\{ - MbModel, - CpuModel, - GpuModel, - PsuModel, - DramModel, - StorageModel, - ChassisModel + use VLW\Database\Tables\Battlestation\{ + MbTable, + CpuTable, + GpuTable, + PsuTable, + DramTable, + StorageTable, + ChassisTable }; - use VLW\API\Databases\VLWdb\Models\Battlestation\Config\{ - MbPsuModel, - MbGpuModel, - MbDramModel, + use VLW\Database\Tables\Battlestation\Config\{ + MbPsuTable, + MbGpuTable, + MbDramTable, ConfigModel, - MbStorageModel, - ChassisMbModel, + MbStorageTable, + ChassisMbTable, MbCpuCoolerModel, MbStorageSlotFormfactorEnum }; @@ -29,22 +29,22 @@ require_once VV::root("api/src/Endpoints.php"); // Load hardware database models - require_once VV::root("api/src/databases/models/Battlestation/Mb.php"); - require_once VV::root("api/src/databases/models/Battlestation/Cpu.php"); - require_once VV::root("api/src/databases/models/Battlestation/Gpu.php"); - require_once VV::root("api/src/databases/models/Battlestation/Psu.php"); - require_once VV::root("api/src/databases/models/Battlestation/Dram.php"); - require_once VV::root("api/src/databases/models/Battlestation/Storage.php"); - require_once VV::root("api/src/databases/models/Battlestation/Chassis.php"); + require_once VV::root("api/src/Database/Tables/Battlestation/Mb.php"); + require_once VV::root("api/src/Database/Tables/Battlestation/Cpu.php"); + require_once VV::root("api/src/Database/Tables/Battlestation/Gpu.php"); + require_once VV::root("api/src/Database/Tables/Battlestation/Psu.php"); + require_once VV::root("api/src/Database/Tables/Battlestation/Dram.php"); + require_once VV::root("api/src/Database/Tables/Battlestation/Storage.php"); + require_once VV::root("api/src/Database/Tables/Battlestation/Chassis.php"); // Load hardware config database models - require_once VV::root("api/src/databases/models/Battlestation/Config/MbPsu.php"); - require_once VV::root("api/src/databases/models/Battlestation/Config/MbGpu.php"); - require_once VV::root("api/src/databases/models/Battlestation/Config/MbDram.php"); - require_once VV::root("api/src/databases/models/Battlestation/Config/Config.php"); - require_once VV::root("api/src/databases/models/Battlestation/Config/MbStorage.php"); - require_once VV::root("api/src/databases/models/Battlestation/Config/ChassisMb.php"); - require_once VV::root("api/src/databases/models/Battlestation/Config/MbCpuCooler.php"); + require_once VV::root("api/src/Database/Tables/Battlestation/Config/MbPsu.php"); + require_once VV::root("api/src/Database/Tables/Battlestation/Config/MbGpu.php"); + require_once VV::root("api/src/Database/Tables/Battlestation/Config/MbDram.php"); + require_once VV::root("api/src/Database/Tables/Battlestation/Config/Config.php"); + require_once VV::root("api/src/Database/Tables/Battlestation/Config/MbStorage.php"); + require_once VV::root("api/src/Database/Tables/Battlestation/Config/ChassisMb.php"); + require_once VV::root("api/src/Database/Tables/Battlestation/Config/MbCpuCooler.php"); const GIGA = 0x3B9ACA00; const MEGA = 0xF4240; @@ -73,7 +73,7 @@ // Get motherboard details by ref_mb_id from config $motherboard = $api->call(Endpoints::BATTLESTATION_MB->value)->params([ - MbModel::ID->value => $config[ChassisMbModel::REF_MB_ID->value] + MbTable::ID->value => $config[ChassisMbTable::REF_MB_ID->value] ])->get()->json()[0]; ?> @@ -89,9 +89,9 @@ data-gpu="" data-dram="" data-case="" - data-drives-mdottwo="value), MbStorageSlotFormfactorEnum::MDOTTWO->value)) ?>" - data-drives-twodotfive="value), MbStorageSlotFormfactorEnum::TWODOTFIVE->value)) ?>" - data-drives-threedotfive="value), MbStorageSlotFormfactorEnum::THREEDOTFIVE->value)) ?>" + data-drives-mdottwo="value), MbStorageSlotFormfactorEnum::MDOTTWO->value)) ?>" + data-drives-twodotfive="value), MbStorageSlotFormfactorEnum::TWODOTFIVE->value)) ?>" + data-drives-threedotfive="value), MbStorageSlotFormfactorEnum::THREEDOTFIVE->value)) ?>" >
@@ -100,38 +100,38 @@

Motherboard

-

value] ?> value] ?>

+

value] ?> value] ?>

-

value] ?>

+

value] ?>

-

value] ?>

+

value] ?>

-

value] ?>

+

value] ?>

-

value] ?? "No LAN" ?>

+

value] ?? "No LAN" ?>

-

value] ?? "No WLAN" ?>

+

value] ?? "No WLAN" ?>

-

value] ?? "No Bluetooth" ?>

+

value] ?? "No Bluetooth" ?>

-

value]) ?>

+

value]) ?>

- value]): ?> + value]): ?>

Yes

@@ -146,35 +146,35 @@ call(Endpoints::BATTLESTATION_CHASSIS->value)->params([ - ChassisModel::ID->value => $mb_chassis[ChassisMbModel::REF_CHASSIS_ID->value] + ChassisTable::ID->value => $mb_chassis[ChassisMbTable::REF_CHASSIS_ID->value] ])->get()->json()[0]; ?>

Case

-

value] ?> value] ?>

+

value] ?> value] ?>

-

value] ?>

+

value] ?>

-

value] ?>

+

value] ?>

-

value] ?>

+

value] ?>

-

value] ?>

+

value] ?>

-

value]) ?>

+

value]) ?>

- value]): ?> + value]): ?>

Yes

@@ -189,47 +189,47 @@ call(Endpoints::BATTLESTATION_CPU->value)->params([ - CpuModel::ID->value => $mb_cpu[MbCpuCoolerModel::REF_CPU_ID->value] + CpuTable::ID->value => $mb_cpu[MbCpuCoolerModel::REF_CPU_ID->value] ])->get()->json()[0]; ?>

CPU

-

value] ?> value] ?>

+

value] ?> value] ?>

-

value] ?>

+

value] ?>

-

value] ?>

+

value] ?>

-

value] ?>

+

value] ?>

-

value] / GIGA ?>GHz

+

value] / GIGA ?>GHz

-

value] / GIGA ?>GHz

+

value] / GIGA ?>GHz

-

value] + $cpu[CpuModel::CORE_COUNT_EFFICIENCY->value] ?> (value] ?>/value] ?>)

+

value] + $cpu[CpuTable::CORE_COUNT_EFFICIENCY->value] ?> (value] ?>/value] ?>)

-

value] ?>

+

value] ?>

-

value]) ?>

+

value]) ?>

- value]): ?> + value]): ?>

Yes

@@ -254,39 +254,39 @@ call(Endpoints::BATTLESTATION_GPU->value)->params([ - GpuModel::ID->value => $mb_gpu[MbGpuModel::REF_GPU_ID->value] + GpuTable::ID->value => $mb_gpu[MbGpuTable::REF_GPU_ID->value] ])->get()->json()[0]; ?>

GPU

-

value] ?> value] ?>

+

value] ?> value] ?>

-

value] ?>

+

value] ?>

-

value] ?>

+

value] ?>

-

value] / GIGA ?>GB

+

value] / GIGA ?>GB

-

value] ?>

+

value] ?>

-

value] ?>

+

value] ?>

-

value]) ?>

+

value]) ?>

- value]): ?> + value]): ?>

Yes

@@ -301,39 +301,39 @@ call(Endpoints::BATTLESTATION_PSU->value)->params([ - PsuModel::ID->value => $mb_psu[MbPsuModel::REF_PSU_ID->value] + PsuTable::ID->value => $mb_psu[MbPsuTable::REF_PSU_ID->value] ])->get()->json()[0]; ?>

PSU

-

value] ?> value] ?> value] ?>W

+

value] ?> value] ?> value] ?>W

-

value] ?>W

+

value] ?>W

-

value] ?>

+

value] ?>

-

value] ?>

+

value] ?>

-

value] === "TRUE" ? "Yes" : "No" ?>

+

value] === "TRUE" ? "Yes" : "No" ?>

-

value] ?? "None" ?>

+

value] ?? "None" ?>

-

value]) ?>

+

value]) ?>

- value]): ?> + value]): ?>

Yes

@@ -354,54 +354,54 @@ call(Endpoints::BATTLESTATION_DRAM->value)->params([ - DramModel::ID->value => $mb_dram[MbDramModel::REF_DRAM_ID->value] + DramTable::ID->value => $mb_dram[MbDramTable::REF_DRAM_ID->value] ])->get()->json()[0]; ?>
-

DRAM - value] ?>

-

value] ?> - value] / GIGA ?>GB - value] / MEGA ?>MHz +

DRAM - value] ?>

+

value] ?> + value] / GIGA ?>GB + value] / MEGA ?>MHz

-

value] / GIGA ?>GB

+

value] / GIGA ?>GB

-

value] / MEGA ?>MHz

+

value] / MEGA ?>MHz

-

value] ?>

+

value] ?>

-

value] ?>

+

value] ?>

-

value] ?>

+

value] ?>

-

value] ?>

+

value] ?>

-

value] === "TRUE" ? "Yes" : "No" ?>

+

value] === "TRUE" ? "Yes" : "No" ?>

-

value] === "TRUE" ? "Yes" : "No" ?>

+

value] === "TRUE" ? "Yes" : "No" ?>

-

value]) ?>

+

value]) ?>

- value]): ?> + value]): ?>

Yes

@@ -411,11 +411,11 @@
-

value] ?>

+

value] ?>

-

value] ?>

+

value] ?>

@@ -433,46 +433,46 @@ call(Endpoints::BATTLESTATION_STORAGE->value)->params([ - StorageModel::ID->value => $mb_storage[MbStorageModel::REF_STORAGE_ID->value] + StorageTable::ID->value => $mb_storage[MbStorageTable::REF_STORAGE_ID->value] ])->get()->json()[0]; ?>
-

value] ?> value] ?>

+

value] ?> value] ?>

- value] ?> - value] / GIGA) ?>GB + value] ?> + value] / GIGA) ?>GB

-

value] ?>

+

value] ?>

-

value] / GIGA) ?>GB

+

value] / GIGA) ?>GB

-

value] ?>

+

value] ?>

-

value] ?>

+

value] ?>

-

value] ?>

+

value] ?>

-

value] ?>

+

value] ?>

-

value]) ?>

+

value]) ?>

- value]): ?> + value]): ?>

Yes

@@ -482,7 +482,7 @@
-

value] ?>

+

value] ?>

diff --git a/public/assets/css/pages/about.css b/public/assets/css/pages/about.css index 8907ddd..47248b3 100644 --- a/public/assets/css/pages/about.css +++ b/public/assets/css/pages/about.css @@ -3,6 +3,7 @@ :root { --primer-color-accent: 148, 255, 21; --color-accent: rgb(var(--primer-color-accent)); + --hue-accent: 390deg; --primer-color-go: 0, 173, 216; --primer-color-php: 79, 93, 149; diff --git a/public/assets/css/pages/contact.css b/public/assets/css/pages/contact.css index bb7b2ff..41f46ae 100644 --- a/public/assets/css/pages/contact.css +++ b/public/assets/css/pages/contact.css @@ -3,6 +3,7 @@ :root { --primer-color-accent: 255, 195, 255; --color-accent: rgb(var(--primer-color-accent)); + --hue-accent: 200deg; } vv-shell { @@ -28,10 +29,6 @@ section.center { text-align: center; } -section.fade { - opacity: .3; -} - /* ## Social */ section.social { @@ -81,11 +78,12 @@ section.pgp { max-width: 800px; position: relative; display: flex; + border-radius: 12px; flex-direction: column; gap: var(--padding); text-align: center; background-color: rgba(var(--primer-color-accent), .15); - padding: calc(var(--padding) * 1.5); + padding: var(--padding); transform: rotate(-1.5deg); } @@ -108,10 +106,6 @@ section.pgp .buttons { gap: var(--padding); } -section.pgp .buttons .download svg.chevron { - transform: unset; -} - /* ## Contact form */ section.form :is(input, textarea) { @@ -200,6 +194,10 @@ section.form-message.sent + section.form { /* # Size queries */ @media (min-width: 460px) { + section.pgp { + padding: calc(var(--padding) * 1.5); + } + section.pgp .buttons { flex-direction: row; justify-content: center; diff --git a/public/assets/css/pages/search.css b/public/assets/css/pages/search.css index ac9f75c..ba4df09 100644 --- a/public/assets/css/pages/search.css +++ b/public/assets/css/pages/search.css @@ -1,85 +1,84 @@ -/* # Overrides */ - -[vv-page="/search"]:not(body) { +vv-shell[vv-page="/search"] { display: flex; flex-direction: column; - gap: var(--padding); + gap: calc(var(--padding) / 2); } -/* # Sections */ - -/* ## Search */ +/* # Search */ section.search { - width: 100%; - display: none; - flex-direction: column; - align-items: center; - gap: var(--padding); - background-color: rgba(255, 255, 255, .05); - padding: calc(var(--padding) * 1.5); - margin-bottom: calc(var(--padding) * 2); -} - -vv-shell[vv-page="/search"] > section.search { display: flex; + gap: var(--padding); + border-radius: 6px; + padding: var(--padding); + background-color: rgba(255, 255, 255, .1); } section.search form { display: contents; } -section.search search { - width: 100%; -} - section.search input { - width: 100%; - border: none; - color: black; - outline: none; - padding: var(--padding); - background-color: var(--color-accent); -} - -section.search button[type="submit"] { - width: 100%; - max-width: 350px; -} - -section.search > svg { - width: 100%; -} - -/* # Search results */ - -section.results .result { - display: flex; - flex-direction: column; - gap: calc(var(--padding) / 2); -} - -/* ## Titles */ - -section.title a h2 { - color: var(--color-accent); -} - -section.title a h2::before { - content: "// "; - color: white; -} - -/* ## Work */ - -section.results.work { - display: grid; - grid-template-columns: 1fr; - gap: calc(var(--padding) / 2); -} - -section.results.work .result { - padding: var(--padding); - background-color: rgba(255, 255, 255, .03); + flex: 1 1 auto; border-radius: 6px; + padding: 0 var(--padding); + border: solid 2px rgba(255, 255, 255, .1); + background-color: rgba(255, 255, 255, .1); +} + +section.search input:focus { + outline: none; + border-color: white; +} + +section.search select { + padding: 5px; + border: none; + background-color: transparent; +} + +section.search select :is(option, optgroup) { + color: black; +} + +/* # Center */ + +section.center { + display: flex; + flex: 1 1 auto; + align-items: center; + flex-direction: column; + justify-content: center; + fill: var(--color-accent); + gap: calc(var(--padding) / 2); +} + +section.center svg { + width: 60px; +} + +/* # Result */ + +section.result { + display: flex; +} + +section.result button { + flex: 1 1 auto; + text-align: left; + padding: var(--padding); +} + +/* # Stats */ + +section.stats { + min-height: calc(var(--padding) * 2); + display: flex; + align-items: center; + gap: calc(var(--padding) / 2); + justify-content: space-between; +} + +vv-shell[vv-page="/search"] section.stats button { + display: none; } \ No newline at end of file diff --git a/public/assets/css/pages/work.css b/public/assets/css/pages/work.css index c9fb172..0cb02c4 100644 --- a/public/assets/css/pages/work.css +++ b/public/assets/css/pages/work.css @@ -3,9 +3,10 @@ :root { --primer-color-accent: 3, 255, 219; --color-accent: rgb(var(--primer-color-accent)); + --hue-accent: 90deg; - --color-reflect: 220, 26, 0; - --color-vegvisir: 0, 128, 255; + --primer-color-reflect: 220, 26, 0; + --primer-color-vegvisir: 0, 128, 255; } vv-shell { @@ -22,7 +23,7 @@ vv-shell { /* ## Hero */ section.hero { - --color-accent: rgba(255, 255, 255); + --color-accent: rgb(255, 255, 255); display: grid; gap: var(--padding); @@ -65,19 +66,21 @@ section.hero .actions { /* ### Vegivisr */ section.hero .item.vegvisir { - --color-accent: var(--color-vegvisir); + --primer-color-accent: var(--primer-color-vegvisir); + --color-accent: rgb(var(--primer-color-vegvisir)); - color: rgb(var(--color-vegvisir)); - background-color: rgba(var(--color-vegvisir), .1); + color: var(--color-vegvisir); + background-color: rgba(var(--primer-color-vegvisir), .1); } /* ### Reflect */ section.hero .item.reflect { - --color-accent: var(--color-reflect); + --primer-color-accent: var(--primer-color-reflect); + --color-accent: rgb(var(--primer-color-reflect)); - color: rgb(var(--color-reflect)); - background-color: rgba(var(--color-reflect), .2); + color: var(--color-reflect); + background-color: rgba(var(--primer-color-reflect), .2); } /* ## Heading */ @@ -90,7 +93,6 @@ section.heading { } section.heading svg { - fill: white; height: 2em; } diff --git a/public/assets/css/pages/work/timeline.css b/public/assets/css/pages/work/timeline.css index d3228d6..3df1e78 100644 --- a/public/assets/css/pages/work/timeline.css +++ b/public/assets/css/pages/work/timeline.css @@ -3,6 +3,7 @@ :root { --primer-color-accent: 3, 255, 219; --color-accent: rgb(var(--primer-color-accent)); + --hue-accent: 90deg; } vv-shell { @@ -27,7 +28,7 @@ section.git { border-radius: 6px; } -section.git svg { +section.git > svg { fill: white; width: 60px; } @@ -120,7 +121,10 @@ section.timeline .items .item img { } section.timeline .items .item .actions { + display: flex; + align-items: baseline; margin-top: 7px; + gap: var(--padding); } /* # Size queries */ @@ -165,6 +169,10 @@ section.timeline .items .item .actions { border-top-color: rgba(var(--primer-color-accent), .2); } + section.timeline .items .item .actions { + flex-direction: column; + } + section.timeline .year:first-of-type .month:first-of-type .day:first-of-type .items .item:first-of-type { margin-top: var(--padding); } diff --git a/public/assets/css/pages/work/wip.css b/public/assets/css/pages/work/wip.css index aa7c498..1e71dc1 100644 --- a/public/assets/css/pages/work/wip.css +++ b/public/assets/css/pages/work/wip.css @@ -3,6 +3,7 @@ :root { --primer-color-accent: 3, 255, 219; --color-accent: rgb(var(--primer-color-accent)); + --hue-accent: 90deg; } vv-shell { diff --git a/public/assets/css/shell.css b/public/assets/css/shell.css index e940adf..3f521a2 100644 --- a/public/assets/css/shell.css +++ b/public/assets/css/shell.css @@ -11,7 +11,8 @@ /* # Cornerstones */ * { - margin: 0; + margin: 0; + fill: inherit; box-sizing: border-box; font-family: "Roboto Mono", sans-serif; color: inherit; @@ -88,6 +89,32 @@ h3 { font-size: 25px; } +/* ## Page transition */ + +[vv-loading] * { + transition: 200ms opacity; +} + +[vv-loading="true"] * { + opacity: 0; + pointer-events: none; +} + +[vv-loading="true"]::after { + content: ""; + position: fixed; + top: 50%; + left: 50%; + width: 45px; + height: 49px; + background-size: contain; + image-rendering: pixelated; + transform: translate(-50%, -50%); + background-image: url("/assets/media/spinner.gif"); + -webkit-filter: hue-rotate(var(--hue-accent)); + filter: hue-rotate(var(--hue-accent)); +} + /* ## Buttons */ button { @@ -118,6 +145,7 @@ button.inline:not(.solid) { } button.inline svg { + flex: none; height: 1em; } @@ -282,6 +310,9 @@ vv-shell { search-results { transition: 500ms opacity, 300ms transform; position: fixed; + display: flex; + flex-direction: column; + gap: var(--padding); top: var(--running-size); right: 0; width: 100%; @@ -296,18 +327,16 @@ search-results { z-index: 50; } -search-results:not([vv-page]) { - display: grid; - align-items: center; - justify-items: center; -} - header.searchboxActive ~ search-results { opacity: 1; pointer-events: all; transform: scale(1); } +search-results section.search { + display: none; +} + /* ### "Start typing" prompt */ search-results .info { @@ -315,11 +344,11 @@ search-results .info { align-items: center; flex-direction: column; margin: auto; - gap: 3svh; + gap: var(--padding); } search-results .info :is(svg, img) { - width: 128px; + width: 60px; fill: var(--color-accent); } diff --git a/public/assets/js/modules/npm/Elevent.mjs b/public/assets/js/modules/npm/Elevent.mjs deleted file mode 120000 index 899f42b..0000000 --- a/public/assets/js/modules/npm/Elevent.mjs +++ /dev/null @@ -1 +0,0 @@ -../../../../../node_modules/elevent/src/Elevent.mjs \ No newline at end of file diff --git a/public/assets/js/shell.js b/public/assets/js/shell.js index 25e6c3e..ae52040 100644 --- a/public/assets/js/shell.js +++ b/public/assets/js/shell.js @@ -2,6 +2,9 @@ import { Elevent } from "/assets/js/modules/npm/Elevent.mjs"; const CLASSNAME_SEARCHBOX_ACTIVE = "searchboxActive"; +// Set global Vegvisir naviation delay for page transition effect +globalThis.vegvisir.globalNavigationDelayMs = 100; + // Handle search box open/close buttons { // Open search box @@ -44,12 +47,10 @@ const CLASSNAME_SEARCHBOX_ACTIVE = "searchboxActive"; clearTimeout(event.target._throttle); event.target._throttle = setTimeout(() => { // Navigate search-results element on user input - new vv.Navigation(`/search?q=${event.target.value}`).navigate(searchResultsElement); + new vv.Navigation(`/search?query=${event.target.value}`).navigate(searchResultsElement); }, 100); }); } // Scroll page to top on navigation -{ - document.addEventListener(vegvisir.Navigation.EVENTS.FINISHED, () => window.scrollTo({top: 0})); -} \ No newline at end of file +document.addEventListener(vegvisir.Navigation.EVENTS.FINISHED, () => window.scrollTo({ top: 0 })); \ No newline at end of file diff --git a/public/assets/media/spinner.gif b/public/assets/media/spinner.gif new file mode 100644 index 0000000..b5975c0 Binary files /dev/null and b/public/assets/media/spinner.gif differ diff --git a/public/contact.php b/public/contact.php index 8d02550..c76edef 100644 --- a/public/contact.php +++ b/public/contact.php @@ -2,58 +2,43 @@ use Vegvisir\Path; - use VLW\Client\API; - use VLW\API\Endpoints; + use VLW\API\{Client, Endpoints}; + use VLW\Database\Tables\Messages\MessagesTable; - use VLW\API\Databases\VLWdb\Models\Messages\MessagesModel; - - require_once VV::root("src/client/API.php"); - require_once VV::root("api/src/Endpoints.php"); - - require_once VV::root("api/src/databases/models/Messages/Messages.php"); - - // Connect to VLW API - $api = new API(); - - class Date extends DateTimeImmutable { - private const AVAILABLE_TO_HOUR = 16; - private const AVAILABLE_FROM_HOUR = 10; - - private const REPLY_TIME_HOURS_AVAILABLE = 2; + require_once VV::root("src/API/API.php"); + require_once VV::root("src/API/Endpoints.php"); + require_once VV::root("src/Database/Tables/Messages/Messages.php"); + $date = new class extends DateTimeImmutable { public function __construct() { // Set DateTime for configured timezone - parent::__construct("now", new DateTimeZone($_ENV["time"]["date_time_zone"])); + parent::__construct("now", new DateTimeZone($_ENV["client_time_available"]["time_zone"])); } // Return current hour in 24-hour format - private function get_hour(): int { + private function hour(): int { return (int) $this->format("G"); } // Returns true if current time is between available from and to hours public function is_available(): bool { - return $this->get_hour() >= self::AVAILABLE_FROM_HOUR && $this->get_hour() < self::AVAILABLE_TO_HOUR; + return $this->hour() >= $_ENV["client_time_available"]["available_from_hour"] && $this->hour() < $_ENV["client_time_available"]["available_to_hour"]; } public function get_estimated_reply_hours(): int { // I'm available! Return the estimated reply time for that if ($this->is_available()) { - return self::REPLY_TIME_HOURS_AVAILABLE; + return $_ENV["client_time_available"]["reply_average_hours"]; } - $hour = $this->get_hour(); - - return $hour < self::AVAILABLE_FROM_HOUR + return $this->hour() < $_ENV["client_time_available"]["available_from_hour"] // Return hours past midnight until I become available (clamped to estimated reply hours) - ? max(self::AVAILABLE_FROM_HOUR - $hour, self::REPLY_TIME_HOURS_AVAILABLE) + ? max($_ENV["client_time_available"]["available_from_hour"] - $this->hour(), $_ENV["client_time_available"]["reply_average_hours"]) // Return hours before midnight until I become available (clamped to estimated reply hours) - : max(self::AVAILABLE_FROM_HOUR + (24 - $hour), self::REPLY_TIME_HOURS_AVAILABLE); + : max($_ENV["client_time_available"]["available_from_hour"] + (24 - $this->hour()), $_ENV["client_time_available"]["reply_average_hours"]); } } - $date = new Date(); - ?>
@@ -103,9 +88,9 @@ call(Endpoints::MESSAGES->value)->post([ - MessagesModel::EMAIL->value => $_POST[MessagesModel::EMAIL->value], - MessagesModel::MESSAGE->value => $_POST[MessagesModel::MESSAGE->value] + $send = (new Client())->call(Endpoints::MESSAGES->value)->post([ + MessagesTable::EMAIL->value => $_POST[MessagesTable::EMAIL->value], + MessagesTable::MESSAGE->value => $_POST[MessagesTable::MESSAGE->value] ]); ?> @@ -128,11 +113,11 @@
- + - + + + + +
- -
-ok): ?> - - json(); ?> - - - value]): ?> -
-

Work

-

value]) ?> search result(s) from my public work

+value, $_GET)): ?> + + search()): ?> +
+

search()) ?> result(s)

+
-
- - value] as $result): ?> -
-

value] ?>

-

value] ?>

-

value]) ?>

+ search() as $result): ?> +
+ +
+ - - call(Endpoints::WORK_ACTIONS->value)->params([WorkActionsModel::REF_WORK_ID->value => $result[WorkModel::ID->value]])->get(); ?> - - - ok): ?> -
- json() as $action): ?> - - - - - -
- - -
- -
- - - - -
- -

No results for search term ""

-
-
- -

Start typing to search

-
+ +
+

0 result(s)

+ +
+
+ +

Nothing to see here, that's a bummer..

+
+ + + +
+ +

Start typing to search

+
+ + + +
+ +

Almost, type at least two letters to search

+
+ + + + + +
+ +

Start typing to search

+
- \ No newline at end of file diff --git a/public/work.php b/public/work.php index 85f7bb8..a33f801 100644 --- a/public/work.php +++ b/public/work.php @@ -1,43 +1,10 @@ resp = $this->call(Endpoints::WORK->value)->params([ - WorkModel::IS_LISTED->value => true - ])->get(); - } - - private function get_item(string $key): array { - $idx = array_search($key, array_column($this->resp->json(), WorkModel::ID->value)); - return $this->resp->json()[$idx]; - } - - public function get_summary(string $key): string { - return $this->resp->ok ? $this->get_item($key)[WorkModel::SUMMARY->value] : self::ERROR_MSG; - } - } + require_once VV::root("src/Consts.php"); + require_once VV::root("src/Database/Models/Work/Work.php"); ?> @@ -48,7 +15,7 @@

vegvisir

-

get_summary("vlw/vegvisir") ?>

+

summary() ?>

-

get_summary("vlw/reflect") ?>

+

summary() ?>

vlw/php-mysql

-

get_summary("vlw/php-mysql") ?>

+

summary() ?>

Website for iCellate Medical

-

get_summary("icellate/website") ?>

+

summary() ?>

Modernizing GeneMate by iCellate

-

get_summary("icellate/genemate") ?>

+

summary() ?>

Custom pages for Deltaco AB

-

get_summary("deltaco/asyncapp") ?>

+

summary() ?>

- - - +
diff --git a/public/work/wip.php b/public/work/wip.php index b43bd06..c2e4ec6 100644 --- a/public/work/wip.php +++ b/public/work/wip.php @@ -1,8 +1,18 @@ +

Soon, very soon!

Bear with me as I cook up some texts about this project! Hopefully with some pictures too.

- +
\ No newline at end of file diff --git a/src/API/API.php b/src/API/API.php new file mode 100644 index 0000000..f3c10f1 --- /dev/null +++ b/src/API/API.php @@ -0,0 +1,18 @@ +db = new MySQL( + $_ENV["server_database"]["host"], + $_ENV["server_database"]["user"], + $_ENV["server_database"]["pass"], + $_ENV["server_database"]["db"], + ); + } + + // Return all rows from a table using $_GET paramters as the WHERE clause as a Reflect\Response + public function list(string $table, array $columns, array $order = null): Response { + $resp = $this->db->for($table)->where(array_intersect_key($_GET, array_flip($columns))); + + // Optionally order rows by columns + if ($order) { + $resp = $resp->order($order); + } + + // Return all matched rows or a 404 with an empty array if there were not results + $resp = $resp->select($columns); + return $resp && $resp->num_rows > 0 + ? new Response($resp->fetch_all(MYSQLI_ASSOC)) + : new Response([], 404); + } + } \ No newline at end of file diff --git a/src/Database/Models/About/Language.php b/src/Database/Models/About/Language.php new file mode 100644 index 0000000..f264b54 --- /dev/null +++ b/src/Database/Models/About/Language.php @@ -0,0 +1,30 @@ +value => $this->id + ]); + } + + public static function all(array $params = []): array { + return array_map(fn(array $item): Language => new Language($item[LanguagesTable::ID->value]), parent::list(Endpoints::ABOUT_LANGUAGES, $params)); + } + + public function bytes(): int { + return $this->get(LanguagesTable::BYTES->value); + } + } \ No newline at end of file diff --git a/src/Database/Models/Model.php b/src/Database/Models/Model.php new file mode 100644 index 0000000..5bcc1a7 --- /dev/null +++ b/src/Database/Models/Model.php @@ -0,0 +1,43 @@ +endpoint) { + $this->assign(self::first(self::list($endpoint, $params))); + } + } + + public static function first(array $array): array { + return $array && is_array(array_values($array)[0]) ? $array[0] : $array; + } + + public static function list(Endpoints $endpoint, array $params = []): array { + $resp = (new Client())->call($endpoint->value)->params($params)->get(); + return $resp->ok ? $resp->json() : []; + } + + public function assign(array $row): self { + $this->row = $row; + return $this; + } + + public function get(string $key): mixed { + return $this->row[$key] ?? null; + } + } \ No newline at end of file diff --git a/src/Database/Models/Search/Search.php b/src/Database/Models/Search/Search.php new file mode 100644 index 0000000..61e9578 --- /dev/null +++ b/src/Database/Models/Search/Search.php @@ -0,0 +1,42 @@ +value => $this->id + ]); + } + + public static function all(array $params = []): array { + return array_map(fn(array $item): Search => new Search($item[SearchTable::ID->value]), parent::list(Endpoints::SEARCH, $params)); + } + + public function title(): ?string { + return $this->get(SearchTable::TITLE->value); + } + + public function summary(): ?string { + return $this->get(SearchTable::SUMMARY->value); + } + + public function category(): ?SearchCategoryEnum { + return SearchCategoryEnum::tryFromName($this->get(SearchTable::CATEGORY->value)); + } + + public function href(): ?string { + return $this->get(SearchTable::HREF->value); + } + } \ No newline at end of file diff --git a/src/Database/Models/Work/Action.php b/src/Database/Models/Work/Action.php new file mode 100644 index 0000000..fa1afb2 --- /dev/null +++ b/src/Database/Models/Work/Action.php @@ -0,0 +1,51 @@ + (new Action())->assign($item), parent::list(Endpoints::WORK_ACTIONS, $params)); + } + + public static function from(Work $work): array { + return self::all([ + ActionsTable::REF_WORK_ID->value => $work->id + ]); + } + + public function icon_prepended(): ?string { + return $this->get(ActionsTable::ICON_PREPENDED->value); + } + + public function icon_appended(): ?string { + return $this->get(ActionsTable::ICON_APPENDED->value); + } + + public function display_text(): string { + return $this->get(ActionsTable::DISPLAY_TEXT->value); + } + + public function href(): ?string { + return $this->get(ActionsTable::HREF->value); + } + + public function classes(): array { + return $this->get(ActionsTable::CLASS_LIST->value) ? explode(",", $this->get(ActionsTable::CLASS_LIST->value)) : []; + } + } \ No newline at end of file diff --git a/src/Database/Models/Work/Tag.php b/src/Database/Models/Work/Tag.php new file mode 100644 index 0000000..fa6dc2f --- /dev/null +++ b/src/Database/Models/Work/Tag.php @@ -0,0 +1,35 @@ + (new Tag())->assign($item), parent::list(Endpoints::WORK_TAGS, $params)); + } + + public static function from(Work $work): array { + return self::all([ + TagsTable::REF_WORK_ID->value => $work->id + ]); + } + + public function label(): TagsLabelEnum { + return TagsLabelEnum::fromName($this->get(TagsTable::LABEL->value)); + } + } \ No newline at end of file diff --git a/src/Database/Models/Work/Timeline.php b/src/Database/Models/Work/Timeline.php new file mode 100644 index 0000000..08c5613 --- /dev/null +++ b/src/Database/Models/Work/Timeline.php @@ -0,0 +1,43 @@ +value => $this->id + ]); + } + + public static function all(array $params = []): array { + return array_map(fn(array $item): Timeline => new Timeline($item[TimelineTable::REF_WORK_ID->value]), parent::list(Endpoints::WORK_TIMELINE, $params)); + } + + public function work(): Work { + return new Work($this->get(TimelineTable::REF_WORK_ID->value)); + } + + public function year(): int { + return $this->get(TimelineTable::YEAR->value); + } + + public function month(): int { + return $this->get(TimelineTable::MONTH->value); + } + + public function day(): int { + return $this->get(TimelineTable::DAY->value); + } + } \ No newline at end of file diff --git a/src/Database/Models/Work/Work.php b/src/Database/Models/Work/Work.php new file mode 100644 index 0000000..c94f643 --- /dev/null +++ b/src/Database/Models/Work/Work.php @@ -0,0 +1,48 @@ +value => $this->id + ]); + } + + public static function all(array $params = []): array { + return array_map(fn(array $item): Work => new Work($item[WorkTable::ID->value]), parent::list(Endpoints::WORK, $params)); + } + + public function title(): ?string { + return $this->get(WorkTable::TITLE->value); + } + + public function summary(): string { + return $this->get(WorkTable::SUMMARY->value); + } + + public function created(): DateTimeImmutable { + return new DateTimeImmutable($this->get(WorkTable::CREATED->value)); + } + + public function tags(): array { + return Tag::from($this); + } + + public function actions(): array { + return Action::from($this); + } + } \ No newline at end of file diff --git a/src/Database/Tables/About/Languages.php b/src/Database/Tables/About/Languages.php new file mode 100644 index 0000000..54f4566 --- /dev/null +++ b/src/Database/Tables/About/Languages.php @@ -0,0 +1,14 @@ +