From 99e9996e932d1580e5ded69ed778c7abf01f4952 Mon Sep 17 00:00:00 2001 From: Victor Westerlund Date: Sun, 7 Jul 2024 10:37:17 +0000 Subject: [PATCH] feat: add "battlestation" (#40) * wip: 2024-06-19T13:31:53+0200 (1718796713) * fix: final touchups with bugfixes * fix: typo in widlcardsearch function * wip: 2024-07-05T14:14:12+0200 (1720181652) * wip: 2024-07-07T11:22:34+0200 (1720344154) * wip: 2024-07-07T11:22:34+0200 (1720344154) --- api/.env.example.ini | 13 +- api/endpoints/battlestation/GET.php | 63 +++ api/endpoints/battlestation/chassis/GET.php | 103 ++++ api/endpoints/battlestation/coolers/GET.php | 108 ++++ api/endpoints/battlestation/cpu/GET.php | 117 +++++ api/endpoints/battlestation/dram/GET.php | 115 ++++ api/endpoints/battlestation/gpu/GET.php | 107 ++++ api/endpoints/battlestation/mb/GET.php | 194 +++++++ api/endpoints/battlestation/psu/GET.php | 102 ++++ api/endpoints/battlestation/storage/GET.php | 110 ++++ api/endpoints/messages/POST.php | 7 +- api/endpoints/search/GET.php | 7 +- api/endpoints/work/DELETE.php | 11 +- api/endpoints/work/GET.php | 11 +- api/endpoints/work/PATCH.php | 9 +- api/endpoints/work/POST.php | 9 +- api/endpoints/work/actions/DELETE.php | 5 +- api/endpoints/work/actions/GET.php | 7 +- api/endpoints/work/actions/POST.php | 7 +- api/endpoints/work/permalinks/GET.php | 7 +- api/endpoints/work/permalinks/POST.php | 9 +- api/endpoints/work/tags/DELETE.php | 7 +- api/endpoints/work/tags/GET.php | 7 +- api/endpoints/work/tags/POST.php | 7 +- api/src/Endpoints.php | 10 + api/src/databases/VLWdb.php | 35 +- .../models/Battlestation/Chassis.php | 19 + .../models/Battlestation/Config/ChassisMb.php | 14 + .../models/Battlestation/Config/Config.php | 15 + .../Battlestation/Config/MbCpuCooler.php | 24 + .../models/Battlestation/Config/MbDram.php | 23 + .../models/Battlestation/Config/MbGpu.php | 14 + .../models/Battlestation/Config/MbPsu.php | 14 + .../models/Battlestation/Config/MbStorage.php | 25 + .../models/Battlestation/Coolers.php | 16 + .../databases/models/Battlestation/Cpu.php | 31 ++ .../databases/models/Battlestation/Dram.php | 37 ++ .../databases/models/Battlestation/Gpu.php | 20 + api/src/databases/models/Battlestation/Mb.php | 30 ++ .../databases/models/Battlestation/Psu.php | 31 ++ .../models/Battlestation/Storage.php | 45 ++ .../css/pages/about/battlestation-retired.css | 30 ++ assets/css/pages/about/battlestation.css | 290 ++++++++++ .../js/pages/about/battlestation-retired.js | 1 + assets/js/pages/about/battlestation.js | 72 +++ assets/media/battlestation.svg | 173 ++++++ assets/media/icons/chevron.svg | 1 + pages/about.php | 1 + pages/about/battlestation-retired.php | 36 ++ pages/about/battlestation.php | 496 ++++++++++++++++++ 50 files changed, 2599 insertions(+), 46 deletions(-) create mode 100644 api/endpoints/battlestation/GET.php create mode 100644 api/endpoints/battlestation/chassis/GET.php create mode 100644 api/endpoints/battlestation/coolers/GET.php create mode 100644 api/endpoints/battlestation/cpu/GET.php create mode 100644 api/endpoints/battlestation/dram/GET.php create mode 100644 api/endpoints/battlestation/gpu/GET.php create mode 100644 api/endpoints/battlestation/mb/GET.php create mode 100644 api/endpoints/battlestation/psu/GET.php create mode 100644 api/endpoints/battlestation/storage/GET.php create mode 100644 api/src/databases/models/Battlestation/Chassis.php create mode 100644 api/src/databases/models/Battlestation/Config/ChassisMb.php create mode 100644 api/src/databases/models/Battlestation/Config/Config.php create mode 100644 api/src/databases/models/Battlestation/Config/MbCpuCooler.php create mode 100644 api/src/databases/models/Battlestation/Config/MbDram.php create mode 100644 api/src/databases/models/Battlestation/Config/MbGpu.php create mode 100644 api/src/databases/models/Battlestation/Config/MbPsu.php create mode 100644 api/src/databases/models/Battlestation/Config/MbStorage.php create mode 100644 api/src/databases/models/Battlestation/Coolers.php create mode 100644 api/src/databases/models/Battlestation/Cpu.php create mode 100644 api/src/databases/models/Battlestation/Dram.php create mode 100644 api/src/databases/models/Battlestation/Gpu.php create mode 100644 api/src/databases/models/Battlestation/Mb.php create mode 100644 api/src/databases/models/Battlestation/Psu.php create mode 100644 api/src/databases/models/Battlestation/Storage.php create mode 100644 assets/css/pages/about/battlestation-retired.css create mode 100644 assets/css/pages/about/battlestation.css create mode 100644 assets/js/pages/about/battlestation-retired.js create mode 100644 assets/js/pages/about/battlestation.js create mode 100644 assets/media/battlestation.svg create mode 100644 assets/media/icons/chevron.svg create mode 100644 pages/about/battlestation-retired.php create mode 100644 pages/about/battlestation.php diff --git a/api/.env.example.ini b/api/.env.example.ini index b149618..3763bb0 100755 --- a/api/.env.example.ini +++ b/api/.env.example.ini @@ -1,8 +1,11 @@ -[vlwdb] -mariadb_host = "" -mariadb_user = "" -mariadb_pass = "" -mariadb_db = "" +[connect] +host = "" +user = "" +pass = "" + +[databases] +vlw = "" +battlestation = "" [github] api_key = "" diff --git a/api/endpoints/battlestation/GET.php b/api/endpoints/battlestation/GET.php new file mode 100644 index 0000000..a02b5cc --- /dev/null +++ b/api/endpoints/battlestation/GET.php @@ -0,0 +1,63 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->GET([ + (new Rules(ConfigModel::REF_MB_ID->value)) + ->type(Type::STRING) + ->min(parent::UUID_LENGTH) + ->max(parent::UUID_LENGTH), + + (new Rules(ConfigModel::FRIENDLY_NAME->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) + ]); + + parent::__construct(Databases::BATTLESTATION, $this->ruleset); + + // Use a copy of search parameters + $this->query = $_GET; + } + + private function get_config(): array { + return $this->results = $this->db + ->for(ConfigModel::TABLE) + ->where($this->query) + ->order([ConfigModel::DATE_BUILT->value => "DESC"]) + ->select(ConfigModel::values()) + ->fetch_all(MYSQLI_ASSOC); + } + + public function main(): Response { + // Set properties as "searchable" + parent::make_wildcard_search(ConfigModel::FRIENDLY_NAME->value, $this->query); + + $this->get_config(); + + // 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/chassis/GET.php b/api/endpoints/battlestation/chassis/GET.php new file mode 100644 index 0000000..b00c21d --- /dev/null +++ b/api/endpoints/battlestation/chassis/GET.php @@ -0,0 +1,103 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->GET([ + (new Rules(ChassisModel::ID->value)) + ->type(Type::STRING) + ->min(parent::UUID_LENGTH) + ->max(parent::UUID_LENGTH), + + (new Rules(ChassisModel::VENDOR_NAME->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(ChassisModel::VENDOR_MODEL->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(ChassisModel::STORAGE_TWOINCHFIVE->value)) + ->type(Type::NUMBER) + ->type(Type::NULL) + ->min(0) + ->max(parent::MYSQL_TINYINT_MAX_LENGTH), + + (new Rules(ChassisModel::STORAGE_THREEINCHFIVE->value)) + ->type(Type::NUMBER) + ->type(Type::NULL) + ->min(0) + ->max(parent::MYSQL_TINYINT_MAX_LENGTH), + + (new Rules(ChassisModel::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(ChassisMbModel::TABLE) + ->where([ChassisMbModel::REF_CHASSIS_ID->value => $result[ChassisModel::ID->value]]) + ->select(ChassisMbModel::values()) + ->fetch_all(MYSQLI_ASSOC); + } + } + + private function get_chassis(): array { + return $this->results = $this->db + ->for(ChassisModel::TABLE) + ->where($this->query) + ->order([ChassisModel::DATE_AQUIRED->value => "DESC"]) + ->select(ChassisModel::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); + + // Get hardware + $this->get_chassis(); + + // 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/coolers/GET.php b/api/endpoints/battlestation/coolers/GET.php new file mode 100644 index 0000000..5d93726 --- /dev/null +++ b/api/endpoints/battlestation/coolers/GET.php @@ -0,0 +1,108 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->GET([ + (new Rules(CoolerModel::ID->value)) + ->type(Type::STRING) + ->min(parent::UUID_LENGTH) + ->max(parent::UUID_LENGTH), + + (new Rules(CoolerModel::TYPE_LIQUID->value)) + ->type(Type::BOOLEAN), + + (new Rules(CoolerModel::SIZE_FAN->value)) + ->type(Type::NULL) + ->type(Type::NUMBER) + ->min(1) + ->max(parent::MYSQL_INTR_MAX_LENGTH), + + (new Rules(CoolerModel::SIZE_RADIATOR->value)) + ->type(Type::NULL) + ->type(Type::NUMBER) + ->min(1) + ->max(parent::MYSQL_INTR_MAX_LENGTH), + + (new Rules(CoolerModel::VENDOR_NAME->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(CoolerModel::VENDOR_MODEL->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(CoolerModel::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(MbCoolerModel::TABLE) + ->where([MbCoolerModel::REF_COOLER_ID->value => $result[CoolerModel::ID->value]]) + ->select(MbCoolerModel::values()) + ->fetch_all(MYSQLI_ASSOC); + } + } + + private function get_coolers(): array { + return $this->results = $this->db + ->for(CoolerModel::TABLE) + ->where($this->query) + ->order([CoolerModel::DATE_AQUIRED->value => "DESC"]) + ->select(CoolerModel::values()) + ->fetch_all(MYSQLI_ASSOC); + } + + public function main(): Response { + // Set properties as "searchable" + parent::make_wildcard_search(CoolerModel::VENDOR_NAME->value, $this->query); + parent::make_wildcard_search(CoolerModel::VENDOR_MODEL->value, $this->query); + + // Get hardware + $this->get_coolers(); + + // 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/cpu/GET.php b/api/endpoints/battlestation/cpu/GET.php new file mode 100644 index 0000000..2bdfb69 --- /dev/null +++ b/api/endpoints/battlestation/cpu/GET.php @@ -0,0 +1,117 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->GET([ + (new Rules(CpuModel::ID->value)) + ->type(Type::STRING) + ->min(parent::UUID_LENGTH) + ->max(parent::UUID_LENGTH), + + (new Rules(CpuModel::CLOCK_BASE->value)) + ->type(Type::NUMBER) + ->min(1), + + (new Rules(CpuModel::CLOCK_TURBO->value)) + ->type(Type::NUMBER) + ->min(1), + + (new Rules(CpuModel::CORE_COUNT_PERFORMANCE->value)) + ->type(Type::NUMBER) + ->min(1) + ->max(parent::MYSQL_TINYINT_MAX_LENGTH), + + (new Rules(CpuModel::CORE_COUNT_EFFICIENCY->value)) + ->type(Type::NUMBER) + ->min(1) + ->max(parent::MYSQL_TINYINT_MAX_LENGTH), + + (new Rules(CpuModel::CORE_THREADS->value)) + ->type(Type::NUMBER) + ->min(1) + ->max(parent::MYSQL_TINYINT_MAX_LENGTH), + + (new Rules(CpuModel::VENDOR_NAME->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(CpuModel::VENDOR_MODEL->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(CpuModel::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(MbCpuCoolerModel::TABLE) + ->where([MbCpuCoolerModel::REF_CPU_ID->value => $result[CpuModel::ID->value]]) + ->select(MbCpuCoolerModel::values()) + ->fetch_all(MYSQLI_ASSOC); + } + } + + private function get_cpu(): array { + return $this->results = $this->db + ->for(CpuModel::TABLE) + ->where($this->query) + ->order([CpuModel::DATE_AQUIRED->value => "DESC"]) + ->select(CpuModel::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); + + // Get hardware + $this->get_cpu(); + + // 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/dram/GET.php b/api/endpoints/battlestation/dram/GET.php new file mode 100644 index 0000000..72539af --- /dev/null +++ b/api/endpoints/battlestation/dram/GET.php @@ -0,0 +1,115 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->GET([ + (new Rules(DramModel::ID->value)) + ->type(Type::STRING) + ->min(parent::UUID_LENGTH) + ->max(parent::UUID_LENGTH), + + (new Rules(DramModel::CAPACITY->value)) + ->type(Type::NUMBER) + ->min(1), + + (new Rules(DramModel::SPEED->value)) + ->type(Type::NUMBER) + ->min(1), + + (new Rules(DramModel::FORMFACTOR->value)) + ->type(Type::ENUM, DramFormfactorEnum::names()), + + (new Rules(DramModel::TECHNOLOGY->value)) + ->type(Type::ENUM, DramTechnologyEnum::names()), + + (new Rules(DramModel::ECC->value)) + ->type(Type::BOOLEAN), + + (new Rules(DramModel::BUFFERED->value)) + ->type(Type::BOOLEAN), + + (new Rules(DramModel::VENDOR_NAME->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(DramModel::VENDOR_MODEL->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(DramModel::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(MbDramModel::TABLE) + ->where([MbDramModel::REF_DRAM_ID->value => $result[DramModel::ID->value]]) + ->select(MbDramModel::values()) + ->fetch_all(MYSQLI_ASSOC); + } + } + + private function get_dram(): array { + return $this->results = $this->db + ->for(DramModel::TABLE) + ->where($this->query) + ->order([DramModel::DATE_AQUIRED->value => "DESC"]) + ->select(DramModel::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); + + // Get hardware + $this->get_dram(); + + // 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/gpu/GET.php b/api/endpoints/battlestation/gpu/GET.php new file mode 100644 index 0000000..5a8598b --- /dev/null +++ b/api/endpoints/battlestation/gpu/GET.php @@ -0,0 +1,107 @@ +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/battlestation/mb/GET.php b/api/endpoints/battlestation/mb/GET.php new file mode 100644 index 0000000..d2c0ea0 --- /dev/null +++ b/api/endpoints/battlestation/mb/GET.php @@ -0,0 +1,194 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->GET([ + (new Rules(MbModel::ID->value)) + ->type(Type::STRING) + ->min(parent::UUID_LENGTH) + ->max(parent::UUID_LENGTH), + + (new Rules(MbModel::FORMFACTOR->value)) + ->type(Type::ENUM, MbFormfactorEnum::names()), + + (new Rules(MbModel::VENDOR_NAME->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(MbModel::VENDOR_MODEL->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(MbModel::NETWORK_ETHERNET->value)) + ->type(Type::NULL) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(MbModel::NETWORK_WLAN->value)) + ->type(Type::NULL) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(MbModel::NETWORK_BLUETOOTH->value)) + ->type(Type::NULL) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(MbModel::IS_RETIRED->value)) + ->type(Type::BOOLEAN) + ]); + + parent::__construct(Databases::BATTLESTATION, $this->ruleset); + + // Use a copy of search parameters + $this->query = $_GET; + } + + private function get_chassis(): void { + 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()) + ->fetch_all(MYSQLI_ASSOC); + } + } + + private function get_psu(): void { + 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()) + ->fetch_all(MYSQLI_ASSOC); + } + } + + private function get_cpu(): void { + 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]]) + ->select(MbCpuCoolerModel::values()) + ->fetch_all(MYSQLI_ASSOC); + } + } + + private function get_gpu(): void { + 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()) + ->fetch_all(MYSQLI_ASSOC); + } + } + + private function get_dram(): void { + 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()) + ->fetch_all(MYSQLI_ASSOC); + } + } + + private function get_storage(): void { + 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()) + ->fetch_all(MYSQLI_ASSOC); + } + } + + // ---- + + private function get_motherboards(): array { + return $this->results = $this->db + ->for(MbModel::TABLE) + ->where($this->query) + ->order([MbModel::DATE_AQUIRED->value => "DESC"]) + ->select(MbModel::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); + + // Get hardware + $this->get_motherboards(); + + // Resolve hardware relationships + $this->get_chassis(); + $this->get_cpu(); + $this->get_psu(); + $this->get_gpu(); + $this->get_dram(); + $this->get_storage(); + + // 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/psu/GET.php b/api/endpoints/battlestation/psu/GET.php new file mode 100644 index 0000000..2420422 --- /dev/null +++ b/api/endpoints/battlestation/psu/GET.php @@ -0,0 +1,102 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->GET([ + (new Rules(PsuModel::ID->value)) + ->type(Type::STRING) + ->min(parent::UUID_LENGTH) + ->max(parent::UUID_LENGTH), + + (new Rules(PsuModel::POWER->value)) + ->type(Type::NUMBER) + ->min(1) + ->max(parent::MYSQL_INT_MAX_LENGTH), + + (new Rules(PsuModel::EIGHTYPLUS_RATING->value)) + ->type(Type::ENUM, EightyplusRatingEnum::names()), + + (new Rules(PsuModel::VENDOR_NAME->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(PsuModel::VENDOR_MODEL->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(PsuModel::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(MbPsuModel::TABLE) + ->where([MbPsuModel::REF_PSU_ID->value => $result[PsuModel::ID->value]]) + ->select(MbPsuModel::values()) + ->fetch_all(MYSQLI_ASSOC); + } + } + + private function get_psu(): array { + return $this->results = $this->db + ->for(PsuModel::TABLE) + ->where($this->query) + ->order([PsuModel::DATE_AQUIRED->value => "DESC"]) + ->select(PsuModel::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); + + // Get hardware + $this->get_psu(); + + // 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/storage/GET.php b/api/endpoints/battlestation/storage/GET.php new file mode 100644 index 0000000..4bfff74 --- /dev/null +++ b/api/endpoints/battlestation/storage/GET.php @@ -0,0 +1,110 @@ +ruleset = new Ruleset(strict: true); + + $this->ruleset->GET([ + (new Rules(StorageModel::ID->value)) + ->type(Type::STRING) + ->min(parent::UUID_LENGTH) + ->max(parent::UUID_LENGTH), + + (new Rules(StorageModel::DISK_TYPE->value)) + ->type(Type::ENUM, StorageDiskTypeEnum::names()), + + (new Rules(StorageModel::DISK_SIZE->value)) + ->type(Type::NUMBER) + ->min(1) + ->max(parent::MYSQL_INT_MAX_LENGTH), + + (new Rules(StorageModel::DISK_INTERFACE->value)) + ->type(Type::ENUM, StorageDiskInterfaceEnum::names()), + + (new Rules(StorageModel::DISK_FORMFACTOR->value)) + ->type(Type::ENUM, StorageDiskFormfactorEnum::names()), + + (new Rules(StorageModel::VENDOR_NAME->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(StorageModel::VENDOR_MODEL->value)) + ->type(Type::STRING) + ->min(1) + ->max(parent::MYSQL_VARCHAR_MAX_LENGTH), + + (new Rules(StorageModel::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(MbStorageModel::TABLE) + ->where([MbStorageModel::REF_STORAGE_ID->value => $result[StorageModel::ID->value]]) + ->select(MbStorageModel::values()) + ->fetch_all(MYSQLI_ASSOC); + } + } + + private function get_storage(): array { + return $this->results = $this->db + ->for(StorageModel::TABLE) + ->where($this->query) + ->order([StorageModel::DATE_AQUIRED->value => "DESC"]) + ->select(StorageModel::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); + + // Get hardware + $this->get_storage(); + + // 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 index 5e52b17..f8761b4 100755 --- a/api/endpoints/messages/POST.php +++ b/api/endpoints/messages/POST.php @@ -6,7 +6,10 @@ use ReflectRules\Rules; use ReflectRules\Ruleset; - use VLW\API\Databases\VLWdb\VLWdb; + use VLW\API\Databases\VLWdb\{ + VLWdb, + Databases + }; use VLW\API\Databases\VLWdb\Models\Messages\MessagesModel; require_once Path::root("src/databases/VLWdb.php"); @@ -31,7 +34,7 @@ ->max(parent::MYSQL_TEXT_MAX_LENGTH) ]); - parent::__construct($this->ruleset); + parent::__construct(Databases::VLW, $this->ruleset); } public function main(): Response { diff --git a/api/endpoints/search/GET.php b/api/endpoints/search/GET.php index 5c2c895..4454263 100755 --- a/api/endpoints/search/GET.php +++ b/api/endpoints/search/GET.php @@ -9,7 +9,10 @@ use VLW\API\Endpoints; - use VLW\API\Databases\VLWdb\VLWdb; + use VLW\API\Databases\VLWdb\{ + VLWdb, + Databases + }; use VLW\API\Databases\VLWdb\Models\Work\WorkModel; require_once Path::root("src/Endpoints.php"); @@ -32,7 +35,7 @@ ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) ]); - parent::__construct($this->ruleset); + parent::__construct(Databases::VLW, $this->ruleset); } private function search_work(): Response { diff --git a/api/endpoints/work/DELETE.php b/api/endpoints/work/DELETE.php index d67c0ce..47b5d29 100755 --- a/api/endpoints/work/DELETE.php +++ b/api/endpoints/work/DELETE.php @@ -7,7 +7,10 @@ use ReflectRules\Ruleset; use const VLW\API\RESP_DELETE_OK; - use VLW\API\Databases\VLWdb\VLWdb; + use VLW\API\Databases\VLWdb\{ + VLWdb, + Databases + }; use VLW\API\Databases\VLWdb\Models\Work\WorkModel; require_once Path::root("src/Endpoints.php"); @@ -45,15 +48,15 @@ (new Rules(WorkModel::DATE_MODIFIED->value)) ->type(Type::NUMBER) ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGHT), + ->max(parent::MYSQL_INT_MAX_LENGTH), (new Rules(WorkModel::DATE_CREATED->value)) ->type(Type::NUMBER) ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGHT) + ->max(parent::MYSQL_INT_MAX_LENGTH) ]); - parent::__construct($this->ruleset); + parent::__construct(Databases::VLW, $this->ruleset); } public function main(): Response { diff --git a/api/endpoints/work/GET.php b/api/endpoints/work/GET.php index 457977e..d42c421 100755 --- a/api/endpoints/work/GET.php +++ b/api/endpoints/work/GET.php @@ -6,7 +6,10 @@ use ReflectRules\Rules; use ReflectRules\Ruleset; - use VLW\API\Databases\VLWdb\VLWdb; + use VLW\API\Databases\VLWdb\{ + VLWdb, + Databases + }; use VLW\API\Databases\VLWdb\Models\Work\WorkModel; require_once Path::root("src/databases/VLWdb.php"); @@ -43,15 +46,15 @@ (new Rules(WorkModel::DATE_MODIFIED->value)) ->type(Type::NUMBER) ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGHT), + ->max(parent::MYSQL_INT_MAX_LENGTH), (new Rules(WorkModel::DATE_CREATED->value)) ->type(Type::NUMBER) ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGHT) + ->max(parent::MYSQL_INT_MAX_LENGTH) ]); - parent::__construct($this->ruleset); + parent::__construct(Databases::VLW, $this->ruleset); } public function main(): Response { diff --git a/api/endpoints/work/PATCH.php b/api/endpoints/work/PATCH.php index ef2c1ca..43eb6a0 100755 --- a/api/endpoints/work/PATCH.php +++ b/api/endpoints/work/PATCH.php @@ -8,7 +8,10 @@ use ReflectRules\Ruleset; use VLW\API\Endpoints; - use VLW\API\Databases\VLWdb\VLWdb; + use VLW\API\Databases\VLWdb\{ + VLWdb, + Databases + }; use VLW\API\Databases\VLWdb\Models\Work\{ WorkModel, WorkPermalinksModel @@ -53,13 +56,13 @@ (new Rules(WorkModel::DATE_MODIFIED->value)) ->type(Type::NUMBER) ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGHT) + ->max(parent::MYSQL_INT_MAX_LENGTH) ->default(time()), (new Rules(WorkModel::DATE_CREATED->value)) ->type(Type::NUMBER) ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGHT) + ->max(parent::MYSQL_INT_MAX_LENGTH) ]); parent::__construct(); diff --git a/api/endpoints/work/POST.php b/api/endpoints/work/POST.php index 143cc4e..74eb7a2 100755 --- a/api/endpoints/work/POST.php +++ b/api/endpoints/work/POST.php @@ -8,7 +8,10 @@ use ReflectRules\Ruleset; use VLW\API\Endpoints; - use VLW\API\Databases\VLWdb\VLWdb; + use VLW\API\Databases\VLWdb\{ + VLWdb, + Databases + }; use VLW\API\Databases\VLWdb\Models\Work\{ WorkModel, WorkPermalinksModel @@ -49,11 +52,11 @@ (new Rules(WorkModel::DATE_CREATED->value)) ->type(Type::NUMBER) ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGHT) + ->max(parent::MYSQL_INT_MAX_LENGTH) ->default(time()) ]); - parent::__construct($this->ruleset); + parent::__construct(Databases::VLW, $this->ruleset); } // Generate a slug URL from string diff --git a/api/endpoints/work/actions/DELETE.php b/api/endpoints/work/actions/DELETE.php index b64194b..a0d582c 100755 --- a/api/endpoints/work/actions/DELETE.php +++ b/api/endpoints/work/actions/DELETE.php @@ -7,7 +7,10 @@ use ReflectRules\Ruleset; use const VLW\API\RESP_DELETE_OK; - use VLW\API\Databases\VLWdb\VLWdb; + use VLW\API\Databases\VLWdb\{ + VLWdb, + Databases + }; use VLW\API\Databases\VLWdb\Models\Work\WorkActionsModel; require_once Path::root("src/databases/VLWdb.php"); diff --git a/api/endpoints/work/actions/GET.php b/api/endpoints/work/actions/GET.php index ccbdb69..e286697 100644 --- a/api/endpoints/work/actions/GET.php +++ b/api/endpoints/work/actions/GET.php @@ -6,7 +6,10 @@ use ReflectRules\Rules; use ReflectRules\Ruleset; - use VLW\API\Databases\VLWdb\VLWdb; + use VLW\API\Databases\VLWdb\{ + VLWdb, + Databases + }; use VLW\API\Databases\VLWdb\Models\Work\WorkActionsModel; require_once Path::root("src/databases/VLWdb.php"); @@ -25,7 +28,7 @@ ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) ]); - parent::__construct($this->ruleset); + parent::__construct(Databases::VLW, $this->ruleset); } public function main(): Response { diff --git a/api/endpoints/work/actions/POST.php b/api/endpoints/work/actions/POST.php index 5587ddc..2a07540 100755 --- a/api/endpoints/work/actions/POST.php +++ b/api/endpoints/work/actions/POST.php @@ -8,7 +8,10 @@ use ReflectRules\Ruleset; use VLW\API\Endpoints; - use VLW\API\Databases\VLWdb\VLWdb; + use VLW\API\Databases\VLWdb\{ + VLWdb, + Databases + }; use VLW\API\Databases\VLWdb\Models\Work\{ WorkModel, WorkActionsModel @@ -54,7 +57,7 @@ ->default(false) ]); - parent::__construct($this->ruleset); + parent::__construct(Databases::VLW, $this->ruleset); } private static function get_entity(): Response { diff --git a/api/endpoints/work/permalinks/GET.php b/api/endpoints/work/permalinks/GET.php index 4c35b6f..d414e14 100755 --- a/api/endpoints/work/permalinks/GET.php +++ b/api/endpoints/work/permalinks/GET.php @@ -6,7 +6,10 @@ use ReflectRules\Rules; use ReflectRules\Ruleset; - use VLW\API\Databases\VLWdb\VLWdb; + use VLW\API\Databases\VLWdb\{ + VLWdb, + Databases + }; use VLW\API\Databases\VLWdb\Models\Work\WorkPermalinksModel; require_once Path::root("src/databases/VLWdb.php"); @@ -30,7 +33,7 @@ ->max(parent::MYSQL_VARCHAR_MAX_LENGTH) ]); - parent::__construct($this->ruleset); + parent::__construct(Databases::VLW, $this->ruleset); } public function main(): Response { diff --git a/api/endpoints/work/permalinks/POST.php b/api/endpoints/work/permalinks/POST.php index 2901217..fd2d904 100755 --- a/api/endpoints/work/permalinks/POST.php +++ b/api/endpoints/work/permalinks/POST.php @@ -7,7 +7,10 @@ use ReflectRules\Rules; use ReflectRules\Ruleset; - use VLW\API\Databases\VLWdb\VLWdb; + use VLW\API\Databases\VLWdb\{ + VLWdb, + Databases + }; use VLW\API\Databases\VLWdb\Models\Work\WorkPermalinksModel; require_once Path::root("src/databases/VLWdb.php"); @@ -35,11 +38,11 @@ (new Rules(WorkPermalinksModel::DATE_CREATED->value)) ->type(Type::NUMBER) ->min(1) - ->max(parent::MYSQL_INT_MAX_LENGHT) + ->max(parent::MYSQL_INT_MAX_LENGTH) ->default(time()) ]); - parent::__construct($this->ruleset); + parent::__construct(Databases::VLW, $this->ruleset); } private static function get_entity(): Response { diff --git a/api/endpoints/work/tags/DELETE.php b/api/endpoints/work/tags/DELETE.php index 2699806..04c6924 100755 --- a/api/endpoints/work/tags/DELETE.php +++ b/api/endpoints/work/tags/DELETE.php @@ -7,7 +7,10 @@ use ReflectRules\Ruleset; use const VLW\API\RESP_DELETE_OK; - use VLW\API\Databases\VLWdb\VLWdb; + use VLW\API\Databases\VLWdb\{ + VLWdb, + Databases + }; use VLW\API\Databases\VLWdb\Models\Work\WorkTagsModel; require_once Path::root("src/databases/VLWdb.php"); @@ -28,7 +31,7 @@ ->type(Type::ENUM, WorkTagsNameEnum::names()) ]); - parent::__construct($this->ruleset); + parent::__construct(Databases::VLW, $this->ruleset); } public function main(): Response { diff --git a/api/endpoints/work/tags/GET.php b/api/endpoints/work/tags/GET.php index 7b6841b..bacfb66 100644 --- a/api/endpoints/work/tags/GET.php +++ b/api/endpoints/work/tags/GET.php @@ -6,7 +6,10 @@ use ReflectRules\Rules; use ReflectRules\Ruleset; - use VLW\API\Databases\VLWdb\VLWdb; + use VLW\API\Databases\VLWdb\{ + VLWdb, + Databases + }; use VLW\API\Databases\VLWdb\Models\Work\{ WorkTagsModel, WorkTagsNameEnum @@ -30,7 +33,7 @@ ->type(Type::ENUM, WorkTagsNameEnum::names()) ]); - parent::__construct($this->ruleset); + parent::__construct(Databases::VLW, $this->ruleset); } public function main(): Response { diff --git a/api/endpoints/work/tags/POST.php b/api/endpoints/work/tags/POST.php index a99205f..e10ae90 100755 --- a/api/endpoints/work/tags/POST.php +++ b/api/endpoints/work/tags/POST.php @@ -8,7 +8,10 @@ use ReflectRules\Ruleset; use VLW\API\Endpoints; - use VLW\API\Databases\VLWdb\VLWdb; + use VLW\API\Databases\VLWdb\{ + VLWdb, + Databases + }; use VLW\API\Databases\VLWdb\Models\Work\{ WorkModel, WorkTagsModel, @@ -37,7 +40,7 @@ ->type(Type::ENUM, WorkTagsNameEnum::names()) ]); - parent::__construct($this->ruleset); + parent::__construct(Databases::VLW, $this->ruleset); } private static function get_entity(): Response { diff --git a/api/src/Endpoints.php b/api/src/Endpoints.php index cb6bb87..7ce9a17 100644 --- a/api/src/Endpoints.php +++ b/api/src/Endpoints.php @@ -14,4 +14,14 @@ case WORK = "/work"; case WORK_TAGS = "/work/tags"; case WORK_ACTIONS = "/work/actions"; + + case BATTLESTATION = "/battlestation"; + case BATTLESTATION_MB = "/battlestation/mb"; + case BATTLESTATION_CPU = "/battlestation/cpu"; + case BATTLESTATION_GPU = "/battlestation/gpu"; + case BATTLESTATION_PSU = "/battlestation/psu"; + case BATTLESTATION_DRAM = "/battlestation/dram"; + case BATTLESTATION_STORAGE = "/battlestation/storage"; + case BATTLESTATION_COOLERS = "/battlestation/coolers"; + case BATTLESTATION_CHASSIS = "/battlestation/chassis"; } \ No newline at end of file diff --git a/api/src/databases/VLWdb.php b/api/src/databases/VLWdb.php index 6fec7a3..2a459af 100755 --- a/api/src/databases/VLWdb.php +++ b/api/src/databases/VLWdb.php @@ -2,7 +2,6 @@ namespace VLW\API\Databases\VLWdb; - use Reflect\ENV; use Reflect\Path; use Reflect\Request; use Reflect\Response; @@ -10,25 +9,31 @@ use libmysqldriver\MySQL; + enum Databases: string { + case VLW = "vlw"; + case BATTLESTATION = "battlestation"; + } + class VLWdb { const UUID_LENGTH = 36; + const MYSQL_INT_MAX_LENGTH = 2147483647; const MYSQL_TEXT_MAX_LENGTH = 65538; const MYSQL_VARCHAR_MAX_LENGTH = 255; - const MYSQL_INT_MAX_LENGHT = 2147483647; + const MYSQL_TINYINT_MAX_LENGTH = 255; protected readonly MySQL $db; - public function __construct(Ruleset $ruleset) { + public function __construct(Databases $database, Ruleset $ruleset) { // Validate provided Ruleset before attempting to connect to the database self::eval_ruleset_or_exit($ruleset); // Create new MariaDB connection $this->db = new MySQL( - $_ENV["vlwdb"]["mariadb_host"], - $_ENV["vlwdb"]["mariadb_user"], - $_ENV["vlwdb"]["mariadb_pass"], - $_ENV["vlwdb"]["mariadb_db"], + $_ENV["connect"]["host"], + $_ENV["connect"]["user"], + $_ENV["connect"]["pass"], + $_ENV["databases"][$database->value], ); } @@ -55,6 +60,22 @@ ); } + // Mutate the value by array key $property_name into a libmysqldriver\MySQL custom operator + // https://github.com/VictorWesterlund/php-libmysqldriver?tab=readme-ov-file#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; + } + // 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; diff --git a/api/src/databases/models/Battlestation/Chassis.php b/api/src/databases/models/Battlestation/Chassis.php new file mode 100644 index 0000000..55a8417 --- /dev/null +++ b/api/src/databases/models/Battlestation/Chassis.php @@ -0,0 +1,19 @@ + div { + margin-top: calc(var(--padding) / 2); + display: flex; + gap: var(--padding); +} + +/* ## Heading */ + +section.heading h1::before, +section.heading h1::after { + opacity: .4; +} + +section.heading h1::before { + content: "“"; +} + +section.heading h1::after { + content: "”"; +} + +/* ## Config */ + +section.config { + position: relative; + display: grid; + grid-template-columns: 300px 1fr; + gap: calc(var(--padding) * 2); +} + +section.config:nth-child(4n+2) { + grid-template-columns: 1fr 300px; +} + +section.config:nth-child(4n+2) > svg { + order: 1; +} + +/* ### PC */ + +section.config > svg { + position: sticky; + top: calc(var(--running-size) + var(--padding)); + width: 100%; +} + +section.config > svg :is(rect, path) { + transition: 300ms; + stroke: white; +} + +section.config > svg.active :is(rect, path), +section.config > svg:hover :is(rect, path) { + opacity: .4; +} + +section.config > svg g.active rect, +section.config > svg g.active path, +section.config > svg g:not(.group):hover rect, +section.config > svg g:not(.group):hover path { + opacity: 1; + stroke: var(--color-accent); +} + +section.config > svg g.active rect, +section.config > svg g:not(.group):hover rect { + filter: drop-shadow(0 0 10px rgba(var(--primer-color-accent), .4)); +} + +/* #### Case */ + +section.config g.case:not(:hover, .active) :is(rect, path) { + opacity: .2; +} + +section.config > svg g.active path, +section.config > svg g:not(.group):hover path { + fill: var(--color-accent); +} + +/* #### Motherboard */ + +section.config > svg .mb .chips { + opacity: 0; +} + +/* #### Active states */ + +section.config > svg g:not(.group) { + display: none; +} + +section.config[data-dram="1"] > svg g.drams g.dram:nth-child(1), +section.config[data-dram="2"] > svg g.drams g.dram:nth-child(3n+1), +section.config[data-dram="3"] > svg g.drams g.dram:nth-child(-n+3), +section.config[data-dram="4"] > svg g.drams g.dram, + +section.config[data-drives-mdottwo="1"] > svg g.mdottwo g.drive:nth-child(1), +section.config[data-drives-mdottwo="2"] > svg g.mdottwo g.drive:nth-child(-n+2), +section.config[data-drives-mdottwo="3"] > svg g.mdottwo g.drive:nth-child(-n+3), + +section.config[data-drives-twodotfive="1"] > svg g.twodotfive g.drive:nth-child(1), +section.config[data-drives-twodotfive="2"] > svg g.twodotfive g.drive:nth-child(-n+2), +section.config[data-drives-twodotfive="3"] > svg g.twodotfive g.drive:nth-child(-n+3), + +section.config[data-drives-threedotfive="1"] > svg g.threedotfive g.drive:nth-child(1), +section.config[data-drives-threedotfive="2"] > svg g.threedotfive g.drive:nth-child(-n+2), +section.config[data-drives-threedotfive="3"] > svg g.threedotfive g.drive:nth-child(-n+3), + +section.config[data-mb="1"] > svg g.mb, +section.config[data-psu="1"] > svg g.psu, +section.config[data-gpu="1"] > svg g.gpu, +section.config[data-cpu="1"] > svg g.cpu, +section.config[data-case="1"] > svg g.case { + display: initial; +} + +/* ## Specs */ + +section.config .specs { + position: relative; + display: flex; + flex-direction: column; + gap: calc(var(--padding) / 2); + border-radius: 6px; +} + +section.config .specs :is(.spec, .group) { + --border-width: 4px; + + transition: 300ms background-color, 300ms border-color, 500ms box-shadow; + padding: calc(var(--padding) - var(--border-width)); + border: solid var(--border-width) transparent; + background-color: rgba(255, 255, 255, .03); + border-radius: 6px; + cursor: pointer; +} + +section.config .specs :is(.spec, .group) * { + pointer-events: none; +} + +/* ### Active state */ + +section.config .specs.active { + background-color: rgba(255, 255, 255, .03); +} + +section.config .specs.active :is(.group, .spec:not(.active)) { + display: none; +} + +/* ### Spec */ + +section.config .specs .spec { + display: flex; + flex-direction: column; +} + +section.config .specs .spec:hover { + border-color: rgba(255, 255, 255, .05); + background-color: rgba(255, 255, 255, .1); + box-shadow: 0 0 30px 10px rgba(255, 255, 255, .05); +} + +section.config .specs .spec.active { + border-color: var(--color-accent); + background-color: rgba(var(--primer-color-accent), .1); + box-shadow: 0 0 30px 10px rgba(var(--primer-color-accent), .05); + cursor: initial; +} + +section.config .specs.active .spec.active { + position: sticky; + top: calc(var(--running-size) + var(--padding)); +} + +section.config .specs .spec h3 { + color: rgba(255, 255, 255, .3); +} + +section.config .specs .spec span { + color: white; +} + +section.config .specs .spec > div { + display: none; + grid-template-columns: repeat(2, 1fr); + gap: calc(var(--padding) / 2); + margin-top: var(--padding); +} + +section.config .specs .spec.active > div { + display: grid; +} + +section.config .specs .spec > div label { + color: var(--color-accent); +} + +section.config .specs .spec > svg { + display: none; + height: calc(var(--padding) / 2); + margin: 0 auto; + margin-top: calc(var(--padding) / 2); + fill: var(--color-accent); +} + +/* ### Group */ + +section.config .specs .group { + display: flex; + justify-content: space-between; + align-items: center; +} + +section.config .specs .group.active { + background-color: rgba(255, 255, 255, .2); +} + +section.config .specs .group:hover { + background-color: rgba(255, 255, 255, .1); +} + +section.config .specs .group.active:hover { + background-color: rgba(255, 255, 255, .3); +} + +section.config .specs .group > svg { + transition: 300ms transform; + fill: var(--color-accent); + height: 10px; +} + +section.config .specs .group.active > svg { + transform: rotateX(180deg); +} + +/* #### Collection */ + +section.config .specs .collection { + display: none; +} + +section.config .specs .group.active + .collection { + display: contents; +} + +/* # Size quries */ + +@media (max-width: 700px) { + section.title > div { + flex-direction: column; + } + + section.config, + section.config:nth-child(4n+2) { + grid-template-columns: 1fr; + } + + section.config > svg { + display: none; + } +} \ No newline at end of file diff --git a/assets/js/pages/about/battlestation-retired.js b/assets/js/pages/about/battlestation-retired.js new file mode 100644 index 0000000..112c1d6 --- /dev/null +++ b/assets/js/pages/about/battlestation-retired.js @@ -0,0 +1 @@ +new vv.Interactions("battlestation-retired"); \ No newline at end of file diff --git a/assets/js/pages/about/battlestation.js b/assets/js/pages/about/battlestation.js new file mode 100644 index 0000000..4550346 --- /dev/null +++ b/assets/js/pages/about/battlestation.js @@ -0,0 +1,72 @@ +new vv.Interactions("battlestation", { + toggleGroup: (event) => { + // Collapse self if already active and current target + if (event.target.classList.contains("active")) { + return event.target.classList.remove("active"); + } + + // Collapse all and open current target + [...event.target.closest(".specs").querySelectorAll(".group")].forEach(element => element.classList.remove("active")); + event.target.classList.add("active"); + }, + setSpecActive: (event) => { + event.target.classList.add("active"); + + event.target.addEventListener("mouseleave", () => event.target.classList.remove("active")); + } +}); + +// Bind hover listeners for components in the SVGs +[...document.querySelectorAll("section.config g:not(.group)")].forEach(element => { + element.addEventListener("mouseenter", () => { + // Find an element in the most adjacent speclist and highlighit it + const target = element.closest("section.config").querySelector(`.spec[data-target="${element.dataset.target}"]`); + // Get closest specs wrapper element + const specsElement = target.closest(".specs"); + // Spec item is part of a collection, we need to expand the group if that is the case + const collectionElement = target.closest(".collection") ?? null; + // Don't close the group after hove ends + let closeGroupOnLeave = false; + + // Set fixed height on .specs wrapper to prevent glitchy page jumping when scrolled + specsElement.style.setProperty("height", `${specsElement.offsetHeight}px`); + target.classList.add("active"); + specsElement.classList.add("active"); + + if (collectionElement) { + // Close the group on leave if the group wasn't active before hovering + closeGroupOnLeave = !collectionElement.previousElementSibling.classList.contains("active"); + + collectionElement.previousElementSibling.classList.add("active"); + } + + // Bind hover leave listener + element.addEventListener("mouseleave", () => { + // Reset to initial states + target.classList.remove("active"); + specsElement.classList.remove("active"); + specsElement.style.removeProperty("height"); + + // Group was closed prior to hover, let's close it on hover leave + if (closeGroupOnLeave) { + collectionElement.previousElementSibling.classList.remove("active"); + } + }, { once: true }); + }); +}); + +// Bind event listeners for components in the spec lists +[...document.querySelectorAll("section.config .spec:not(.group)")].forEach(element => { + element.addEventListener("mouseenter", () => { + const svgTarget = element.closest("section.config").querySelector(`svg`); + const target = svgTarget.querySelector(`svg g[data-target="${element.dataset.target}"]`); + + svgTarget.classList.add("active"); + target.classList.add("active"); + + element.addEventListener("mouseleave", () => { + svgTarget.classList.remove("active"); + target.classList.remove("active"); + }, { once: true }); + }); +}); \ No newline at end of file diff --git a/assets/media/battlestation.svg b/assets/media/battlestation.svg new file mode 100644 index 0000000..633f67d --- /dev/null +++ b/assets/media/battlestation.svg @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/media/icons/chevron.svg b/assets/media/icons/chevron.svg new file mode 100644 index 0000000..056ac62 --- /dev/null +++ b/assets/media/icons/chevron.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pages/about.php b/pages/about.php index be4e777..1009701 100755 --- a/pages/about.php +++ b/pages/about.php @@ -16,6 +16,7 @@

Personal

With a cup of coffee ready at hand, I can at times become a real armchair detective for a variety of nerdy topics I find interesting, and spend hours reading as much as I can about them too. I like to skii when I'm not glued in front of a computer screen.

+

I do occationally game, and I also have a silent passion that comes out every few years for building gaming computers - even servers recently!

Projects

diff --git a/pages/about/battlestation-retired.php b/pages/about/battlestation-retired.php new file mode 100644 index 0000000..8683a6e --- /dev/null +++ b/pages/about/battlestation-retired.php @@ -0,0 +1,36 @@ + +
+

Retired components

+

I'd be happy to send you any component that you find here for "free". The only thing I ask in return is that you pay for shipping.

+

This page is still a work-in-progress. You can use my API to get a list of retired components by hardware category for now.

+
+
+ battlestation/chassis?is_retired=true" target="_blank"> + + + battlestation/cpu?is_retired=true" target="_blank"> + + + battlestation/gpu?is_retired=true" target="_blank"> + + + battlestation/mb?is_retired=true" target="_blank"> + + + battlestation/psu?is_retired=true" target="_blank"> + + + battlestation/storage?is_retired=true" target="_blank"> + + +
+
+

Found something you like?

+

Please note; I can't guarantee the thing you want will work as expected, or work at all! But I will test the compontent for you if I still have means at hand to do so.

+
+
+ + + +
+ \ No newline at end of file diff --git a/pages/about/battlestation.php b/pages/about/battlestation.php new file mode 100644 index 0000000..563dc6f --- /dev/null +++ b/pages/about/battlestation.php @@ -0,0 +1,496 @@ +call(Endpoints::BATTLESTATION->value)->get(); + +?> + +ok): ?> +
+

Battle­stations

+

I'd be happy to send you, dear reader, any component that you find here for "free" that hasn't been retired yet. The only thing I ask in return is that you pay for shipping.

+

I can't guarantee the thing you want will work as expected, or work at all! But I will test the compontent for you if I still have means at hand to do so.

+
+ + +
+
+ + json() as $config): ?> + + call(Endpoints::BATTLESTATION_MB->value)->params([ + MbModel::ID->value => $config[ChassisMbModel::REF_MB_ID->value] + ])->get()->json()[0]; + + ?> + +
+

value] ?? "Lucious" ?>

+

This rig was built: value]) ?>

+
+
" + data-psu="" + 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)) ?>" + > + +
+ + + +
+

Motherboard

+

value] ?> value] ?>

+
+
+ +

value] ?>

+
+
+ +

value] ?>

+
+
+ +

value] ?>

+
+
+ +

value] ?? "No LAN" ?>

+
+
+ +

value] ?? "No WLAN" ?>

+
+
+ +

value] ?? "No Bluetooth" ?>

+
+
+ +

value]) ?>

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

Yes

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

Case

+

value] ?> value] ?>

+
+
+ +

value] ?>

+
+
+ +

value] ?>

+
+
+ +

value] ?>

+
+
+ +

value] ?>

+
+
+ +

value]) ?>

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

Yes

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

CPU

+

value] ?> value] ?>

+
+
+ +

value] ?>

+
+
+ +

value] ?>

+
+
+ +

value] ?>

+
+
+ +

value] / GIGA ?>GHz

+
+
+ +

value] / GIGA ?>GHz

+
+
+ +

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

+
+
+ +

value] ?>

+
+
+ +

value]) ?>

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

Yes

+
+ +
+
+
+ +

value] ?>

+
+
+ +

value] ?>

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

GPU

+

value] ?> value] ?>

+
+
+ +

value] ?>

+
+
+ +

value] ?>

+
+
+ +

value] / GIGA ?>GB

+
+
+ +

value] ?>

+
+
+ +

value] ?>

+
+
+ +

value]) ?>

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

Yes

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

PSU

+

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

+
+
+ +

value] ?>W

+
+
+ +

value] ?>

+
+
+ +

value] ?>

+
+
+ +

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

+
+
+ +

value] ?? "None" ?>

+
+
+ +

value]) ?>

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

Yes

+
+ +
+
+ + +
+

DRAM

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

DRAM - value] ?>

+

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

+
+
+ +

value] / GIGA ?>GB

+
+
+ +

value] / MEGA ?>MHz

+
+
+ +

value] ?>

+
+
+ +

value] ?>

+
+
+ +

value] ?>

+
+
+ +

value] ?>

+
+
+ +

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

+
+
+ +

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

+
+
+ +

value]) ?>

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

Yes

+
+ +
+
+
+ +

value] ?>

+
+
+ +

value] ?>

+
+
+
+ +
+ +
+

Storage

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

value] ?> value] ?>

+

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

+
+
+ +

value] ?>

+
+
+ +

value] / GIGA) ?>GB

+
+
+ +

value] ?>

+
+
+ +

value] ?>

+
+
+ +

value] ?>

+
+
+ +

value] ?>

+
+
+ +

value]) ?>

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

Yes

+
+ +
+
+
+ +

value] ?>

+
+
+
+ +
+ +
+
+ + + \ No newline at end of file