mirror of
https://codeberg.org/vlw/vlw.se.git
synced 2025-09-13 21:13:40 +02:00
Compare commits
41 commits
8cbf2248c4
...
675f3962af
Author | SHA1 | Date | |
---|---|---|---|
675f3962af | |||
ff7d4f5397 | |||
07e0046828 | |||
18e1ae5088 | |||
9e3c549e0a | |||
80c6579136 | |||
1ac2704124 | |||
f551d5d889 | |||
51e8215e78 | |||
7f4b54685e | |||
6dad22f226 | |||
0483e092dd | |||
ae1e992c5f | |||
43ddf1fdf6 | |||
903a14b3ae | |||
99e9996e93 | |||
7cd41d3e13 | |||
bef1bab522 | |||
285bdc3980 | |||
ce1f3e3bab | |||
32f13b356a | |||
608f775f24 | |||
e1e4c3fd1a | |||
0478685791 | |||
fd04c3d5ae | |||
9b3ab0b17b | |||
a7655f9cdb | |||
412a457bee | |||
982acc6a40 | |||
3fd7ce6bf0 | |||
e1e3a4a68a | |||
87cf63c884 | |||
3154afab3c | |||
f963a8993d | |||
3f944e1a33 | |||
94d20d51ae | |||
a7d2730aac | |||
8dccd20d39 | |||
a82c853153 | |||
6ef024db6f | |||
a8a1c5791f |
149 changed files with 4501 additions and 2966 deletions
|
@ -2,3 +2,6 @@
|
|||
base_url = "https://api.vlw.one/"
|
||||
api_key = ""
|
||||
verify_peer = 0
|
||||
|
||||
[time]
|
||||
date_time_zone = "Europe/Stockholm"
|
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -1,8 +1,14 @@
|
|||
assets/media/content
|
||||
# Public assets #
|
||||
#################
|
||||
public/robots.txt
|
||||
public/.well-known
|
||||
|
||||
assets/js/modules/npm
|
||||
|
||||
# Bootstrapping #
|
||||
#################
|
||||
vendor
|
||||
node_modules
|
||||
.env.ini
|
||||
|
||||
# OS generated files #
|
||||
|
|
34
README.md
34
README.md
|
@ -1,27 +1,38 @@
|
|||
# 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://github.com/victorwesterlund/vegvisir) and my [Reflect API framework](https://github.com/victorwesterlund/reflect).
|
||||
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).
|
||||
|
||||
# Installation
|
||||
If you for whatever reason want to get this website up and running for yourself this is how that is done.
|
||||
|
||||
This website is built for PHP 8.0+ and MariaDB 14+ (for the API database).
|
||||
## 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/)
|
||||
|
||||
**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)
|
||||
|
||||
## Website (Vegvisir)
|
||||
1. **Download this repo**
|
||||
|
||||
Git clone or download this repo to any local folder
|
||||
```
|
||||
git clone https://github.com/VictorWesterlund/vlw.se
|
||||
git clone https://codeberg.org/vlw/vlw.se
|
||||
```
|
||||
2. **Download and install Vegvisir**
|
||||
|
||||
Follow the installation instructions for [Vegvisir](https://github.com/victorwesterlund/vegvisir) and point the `site_path` variable to the local vlw.se folder.
|
||||
Follow the installation instructions for [Vegvisir](https://vegvisir.vlw.se/docs/installation) and point the `root_path` variable to your local vlw.se folder.
|
||||
|
||||
3. **Install dependencies**
|
||||
3. **Run the install script**
|
||||
|
||||
Install dependencies with composer.
|
||||
This bash script will install dependencies and make npm modules public.
|
||||
```
|
||||
composer install --optimize-autoloader
|
||||
./install.sh
|
||||
```
|
||||
|
||||
Et voila! You probably want to install the API-side too but the website itself should now be accessible from your configured Vegvisir host.
|
||||
|
@ -35,16 +46,16 @@ The API (and database) is where most content is stored and served from on this w
|
|||
|
||||
Otherwise... Git clone or download this repo to any local folder
|
||||
```
|
||||
git clone https://github.com/VictorWesterlund/vlw.se
|
||||
git clone https://codeberg.org/vlw/vlw.se
|
||||
```
|
||||
|
||||
2. **Download and install Reflect**
|
||||
|
||||
Follow the installation instructions for [Reflect](https://github.com/victorwesterlund/vegvisir) and point the `endpoints` variable to the `/api` subdirectory in the local vlw.se folder.
|
||||
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.
|
||||
|
||||
3. **Install dependencies**
|
||||
|
||||
Install dependencies with composer.
|
||||
`cd` into the api folder and install dependencies with composer.
|
||||
```
|
||||
composer install --optimize-autoloader
|
||||
```
|
||||
|
@ -57,9 +68,6 @@ The API (and database) is where most content is stored and served from on this w
|
|||
|
||||
Make a copy of `/api/.env.example.ini` and change the `[vlwdb]` variables with your MariaDB credentials.
|
||||
|
||||
You also have to generate a [GitHub access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) if you wish to use the `releases` endpoint.
|
||||
[Read more about this endpoint here](#)
|
||||
|
||||
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.
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
[vlwdb]
|
||||
mariadb_host = ""
|
||||
mariadb_user = ""
|
||||
mariadb_pass = ""
|
||||
mariadb_db = ""
|
||||
[connect]
|
||||
host = ""
|
||||
user = ""
|
||||
pass = ""
|
||||
|
||||
[github]
|
||||
api_key = ""
|
||||
# Use-Agent string sent to GitHub API
|
||||
# They recommend setting it to your GitHub username or app name
|
||||
user_agent = ""
|
||||
[databases]
|
||||
vlw = ""
|
||||
battlestation = ""
|
||||
|
||||
; 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 = ""
|
|
@ -1,14 +1,8 @@
|
|||
{
|
||||
"require": {
|
||||
"local/api.endpoints": "1.0.0-dev",
|
||||
"reflect/plugin-rules": "^1.5",
|
||||
"victorwesterlund/xenum": "^1.1"
|
||||
"victorwesterlund/xenum": "dev-master",
|
||||
"vlw/mysql": "dev-master"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"repositories": [
|
||||
{
|
||||
"type": "path",
|
||||
"url": "src/packages/Endpoints"
|
||||
}
|
||||
]
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
|
|
61
api/composer.lock
generated
61
api/composer.lock
generated
|
@ -4,33 +4,8 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "9da96ba90ef20d885034442b30dce0a3",
|
||||
"content-hash": "f3f2b3cb3bd789eee6af4a93f4a6e0f9",
|
||||
"packages": [
|
||||
{
|
||||
"name": "local/api.endpoints",
|
||||
"version": "1.0.0-dev",
|
||||
"dist": {
|
||||
"type": "path",
|
||||
"url": "src/packages/Endpoints",
|
||||
"reference": "89b7b9a4cc504abddb4aeec8e05a95c9d9087575"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"VLW\\API\\": "src/"
|
||||
}
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Victor Westerlund",
|
||||
"email": "victor@vlw.se"
|
||||
}
|
||||
],
|
||||
"description": "Endpoint pathmappings for VLW API",
|
||||
"transport-options": {
|
||||
"relative": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "reflect/plugin-rules",
|
||||
"version": "1.5.0",
|
||||
|
@ -70,7 +45,7 @@
|
|||
},
|
||||
{
|
||||
"name": "victorwesterlund/xenum",
|
||||
"version": "1.1.1",
|
||||
"version": "dev-master",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/VictorWesterlund/php-xenum.git",
|
||||
|
@ -82,6 +57,7 @@
|
|||
"reference": "8972f06f42abd1f382807a67e937d5564bb89699",
|
||||
"shasum": ""
|
||||
},
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@ -104,13 +80,42 @@
|
|||
"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": {
|
||||
"local/api.endpoints": 20
|
||||
"victorwesterlund/xenum": 20,
|
||||
"vlw/mysql": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
|
|
29
api/endpoints/about/languages/GET.php
Normal file
29
api/endpoints/about/languages/GET.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Call;
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
|
||||
use VLW\API\Endpoints;
|
||||
|
||||
require_once Path::root("src/Endpoints.php");
|
||||
|
||||
class GET_AboutLanguages {
|
||||
public function __construct() {}
|
||||
|
||||
private static function cache_exists(): bool {
|
||||
return file_exists($_ENV["about_languages"]["cache_file"]);
|
||||
}
|
||||
|
||||
private static function load_cache(): array {
|
||||
return json_decode(file_get_contents($_GET["about_languages"]["cache_file"]));
|
||||
}
|
||||
|
||||
public function main(): Response {
|
||||
$bitch = (new Call(Endpoints::ABOUT_LANGUAGES->value))->post(["FUCK YOU"]);
|
||||
// Return 404 Not Found if response array is empty
|
||||
return self::cache_exists()
|
||||
? new Response(self::load_cache())
|
||||
: $bitch;
|
||||
}
|
||||
}
|
13
api/endpoints/about/languages/POST.php
Normal file
13
api/endpoints/about/languages/POST.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Call;
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
|
||||
class POST_AboutLanguages {
|
||||
public function __construct() {}
|
||||
|
||||
public function main(): Response {
|
||||
return new Response("We have a problem with Reflect again");
|
||||
}
|
||||
}
|
63
api/endpoints/battlestation/GET.php
Normal file
63
api/endpoints/battlestation/GET.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\Config\ConfigModel;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Config/Config.php");
|
||||
|
||||
class GET_Battlestation extends VLWdb {
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
private array $query;
|
||||
private array $results = [];
|
||||
|
||||
public function __construct() {
|
||||
$this->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);
|
||||
}
|
||||
}
|
103
api/endpoints/battlestation/chassis/GET.php
Normal file
103
api/endpoints/battlestation/chassis/GET.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\ChassisModel;
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\Config\ChassisMbModel;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Chassis.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Config/ChassisMb.php");
|
||||
|
||||
class GET_BattlestationChassis extends VLWdb {
|
||||
private const REL_MOTHERBOARDS = "motherboards";
|
||||
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
private array $query;
|
||||
private array $results = [];
|
||||
|
||||
public function __construct() {
|
||||
$this->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);
|
||||
}
|
||||
}
|
108
api/endpoints/battlestation/coolers/GET.php
Normal file
108
api/endpoints/battlestation/coolers/GET.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\{
|
||||
CoolerModel
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\Config\MbCpuCoolerModel;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Coolers.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Config/MbCpuCooler.php");
|
||||
|
||||
class GET_BattlestationCoolers extends VLWdb {
|
||||
private const REL_MOTHERBOARDS = "motherboards";
|
||||
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
private array $query;
|
||||
private array $results = [];
|
||||
|
||||
public function __construct() {
|
||||
$this->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);
|
||||
}
|
||||
}
|
117
api/endpoints/battlestation/cpu/GET.php
Normal file
117
api/endpoints/battlestation/cpu/GET.php
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\{
|
||||
CpuModel,
|
||||
ClassEnum
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\Config\MbCpuCoolerModel;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Cpu.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Config/MbCpuCooler.php");
|
||||
|
||||
class GET_BattlestationCpu extends VLWdb {
|
||||
private const REL_MOTHERBOARDS = "motherboards";
|
||||
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
private array $query;
|
||||
private array $results = [];
|
||||
|
||||
public function __construct() {
|
||||
$this->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);
|
||||
}
|
||||
}
|
115
api/endpoints/battlestation/dram/GET.php
Normal file
115
api/endpoints/battlestation/dram/GET.php
Normal file
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\{
|
||||
DramModel,
|
||||
DramFormfactorEnum,
|
||||
DramTechnologyEnum
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\Config\MbDramModel;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Dram.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Config/MbDram.php");
|
||||
|
||||
class GET_BattlestationDram extends VLWdb {
|
||||
private const REL_MOTHERBOARDS = "motherboards";
|
||||
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
private array $query;
|
||||
private array $results = [];
|
||||
|
||||
public function __construct() {
|
||||
$this->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);
|
||||
}
|
||||
}
|
107
api/endpoints/battlestation/gpu/GET.php
Normal file
107
api/endpoints/battlestation/gpu/GET.php
Normal file
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\GpuModel;
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\Config\MbGpuModel;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Gpu.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Config/MbGpu.php");
|
||||
|
||||
class GET_BattlestationGpu extends VLWdb {
|
||||
private const REL_MOTHERBOARDS = "motherboards";
|
||||
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
private array $query;
|
||||
private array $results = [];
|
||||
|
||||
public function __construct() {
|
||||
$this->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);
|
||||
}
|
||||
}
|
194
api/endpoints/battlestation/mb/GET.php
Normal file
194
api/endpoints/battlestation/mb/GET.php
Normal file
|
@ -0,0 +1,194 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\{
|
||||
MbModel,
|
||||
MbFormfactorEnum
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\Config\{
|
||||
MbGpuModel,
|
||||
MbPsuModel,
|
||||
MbDramModel,
|
||||
MbStorageModel,
|
||||
ChassisMbModel,
|
||||
MbCpuCoolerModel
|
||||
};
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Mb.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Config/MbPsu.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Config/MbGpu.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Config/MbDram.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Config/MbStorage.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Config/ChassisMb.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Config/MbCpuCooler.php");
|
||||
|
||||
class GET_BattlestationMb extends VLWdb {
|
||||
private const REL_CPU = "cpus";
|
||||
private const REL_PSU = "psus";
|
||||
private const REL_GPU = "gpus";
|
||||
private const REL_DRAM = "dram";
|
||||
private const REL_STORAGE = "storage";
|
||||
private const REL_CHASSIS = "chassis";
|
||||
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
private array $query;
|
||||
private array $results = [];
|
||||
|
||||
public function __construct() {
|
||||
$this->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);
|
||||
}
|
||||
}
|
102
api/endpoints/battlestation/psu/GET.php
Normal file
102
api/endpoints/battlestation/psu/GET.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\{
|
||||
PsuModel,
|
||||
EightyplusRatingEnum
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\Config\MbPsuModel;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Psu.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Config/MbPsu.php");
|
||||
|
||||
class GET_BattlestationPsu extends VLWdb {
|
||||
private const REL_MOTHERBOARDS = "motherboards";
|
||||
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
private array $query;
|
||||
private array $results = [];
|
||||
|
||||
public function __construct() {
|
||||
$this->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);
|
||||
}
|
||||
}
|
110
api/endpoints/battlestation/storage/GET.php
Normal file
110
api/endpoints/battlestation/storage/GET.php
Normal file
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\{
|
||||
StorageModel,
|
||||
StorageDiskTypeEnum,
|
||||
StorageDiskInterfaceEnum,
|
||||
StorageDiskFormfactorEnum
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\Config\MbStorageModel;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Storage.php");
|
||||
require_once Path::root("src/databases/models/Battlestation/Config/MbStorage.php");
|
||||
|
||||
class GET_BattlestationStorage extends VLWdb {
|
||||
private const REL_MOTHERBOARDS = "motherboards";
|
||||
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
private array $query;
|
||||
private array $results = [];
|
||||
|
||||
public function __construct() {
|
||||
$this->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);
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
|
||||
use VLW\API\Databases\VLWdb\VLWdb;
|
||||
use VLW\API\Databases\VLWdb\Models\Coffee\CoffeeModel;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Coffee.php");
|
||||
|
||||
class GET_Coffee extends VLWdb {
|
||||
const LIST_LIMIT = 20;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to get work data, please try again later", 503);
|
||||
}
|
||||
|
||||
public function main(): Response {
|
||||
// Get the last LIST_LIMIT coffees from the database
|
||||
$resp = $this->db->for(CoffeeModel::TABLE)
|
||||
->order([CoffeeModel::DATE_TIMESTAMP_CREATED->value => "DESC"])
|
||||
->limit(self::LIST_LIMIT)
|
||||
->select([
|
||||
CoffeeModel::ID->value,
|
||||
CoffeeModel::DATE_TIMESTAMP_CREATED->value
|
||||
]);
|
||||
|
||||
return parent::is_mysqli_result($resp)
|
||||
? new Response($resp->fetch_all(MYSQLI_ASSOC))
|
||||
: $this->resp_database_error();
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
|
||||
use VLW\API\Databases\VLWdb\VLWdb;
|
||||
use VLW\API\Databases\VLWdb\Models\Coffee\CoffeeModel;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Coffee.php");
|
||||
|
||||
class POST_Coffee extends VLWdb {
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to record coffee! Ugh please take a note somewhere else", 503);
|
||||
}
|
||||
|
||||
public function main(): Response {
|
||||
// Generate UUID for entity
|
||||
$id = parent::gen_uuid4();
|
||||
|
||||
// Attempt to create new entity
|
||||
$insert = $this->db->for(CoffeeModel::TABLE)
|
||||
->insert([
|
||||
CoffeeModel::ID->value => $id,
|
||||
CoffeeModel::DATE_TIMESTAMP_CREATED->value => time(),
|
||||
]);
|
||||
|
||||
// Return 201 Created and entity id if successful
|
||||
return $insert ? new Response($id, 201) : $this->resp_database_error();
|
||||
}
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use VLW\API\Databases\VLWdb\VLWdb;
|
||||
use VLW\API\Databases\VLWdb\Models\Media\MediaModel;
|
||||
|
||||
use victorwesterlund\xEnum;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Media.php");
|
||||
|
||||
enum MediaDispositionEnum: string {
|
||||
use xEnum;
|
||||
|
||||
case METADATA = "metadata";
|
||||
case INLINE = "inline";
|
||||
case DOWNLOAD = "download";
|
||||
}
|
||||
|
||||
class GET_Media extends VLWdb {
|
||||
const GET_DISPOSITION_KEY = "disposition";
|
||||
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->ruleset = new Ruleset(strict: true);
|
||||
|
||||
$this->ruleset->GET([
|
||||
(new Rules(MediaModel::ID->value))
|
||||
->required()
|
||||
->type(Type::STRING)
|
||||
->min(1)
|
||||
->max(parent::MYSQL_VARCHAR_MAX_LENGTH),
|
||||
|
||||
(new Rules(self::GET_DISPOSITION_KEY))
|
||||
->type(Type::ENUM, MediaDispositionEnum::values())
|
||||
->default(MediaDispositionEnum::METADATA->value)
|
||||
]);
|
||||
}
|
||||
|
||||
// # Helper methods
|
||||
|
||||
private function fetch_srcset(string $id): array {
|
||||
$resp = $this->db->for(WorkTagsModel::TABLE)
|
||||
->where([WorkTagsModel::ANCHOR->value => $id])
|
||||
->select(WorkTagsModel::NAME->value);
|
||||
|
||||
return parent::is_mysqli_result($resp) ? $resp->fetch_all(MYSQLI_ASSOC) : [];
|
||||
}
|
||||
|
||||
// # Responses
|
||||
|
||||
// Return 422 Unprocessable Content error if request validation failed
|
||||
private function resp_rules_invalid(): Response {
|
||||
return new Response($this->ruleset->get_errors(), 422);
|
||||
}
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to get work data, please try again later", 503);
|
||||
}
|
||||
|
||||
public function main(): Response {
|
||||
// Bail out if request validation failed
|
||||
if (!$this->ruleset->is_valid()) {
|
||||
return $this->resp_rules_invalid();
|
||||
}
|
||||
|
||||
$resp = $this->db->for(MediaModel::TABLE)
|
||||
->where([MediaModel::ID->value => $_GET[MediaModel::ID->value]])
|
||||
->select([
|
||||
MediaModel::ID->value,
|
||||
MediaModel::NAME->value,
|
||||
MediaModel::TYPE->value,
|
||||
MediaModel::MIME->value,
|
||||
MediaModel::EXTENSION->value,
|
||||
MediaModel::SRCSET->value,
|
||||
MediaModel::DATE_TIMESTAMP_CREATED->value,
|
||||
]);
|
||||
|
||||
// Bail out if something went wrong retrieving rows from the database
|
||||
if (!parent::is_mysqli_result($resp)) {
|
||||
return $this->resp_database_error();
|
||||
}
|
||||
|
||||
$media = $resp->fetch_assoc();
|
||||
$test = true;
|
||||
}
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use Reflect\Method;
|
||||
use function Reflect\Call;
|
||||
|
||||
use VLW\API\Databases\VLWdb\VLWdb;
|
||||
use VLW\API\Databases\VLWdb\Models\Media\MediaModel;
|
||||
use VLW\API\Databases\VLWdb\Models\Media\MediaTypeEnum;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Media.php");
|
||||
|
||||
class POST_Media extends VLWdb {
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->ruleset = new Ruleset(strict: true);
|
||||
|
||||
$this->ruleset->POST([
|
||||
(new Rules(MediaModel::ID->value))
|
||||
->type(Type::STRING)
|
||||
->min(1)
|
||||
->max(parent::MYSQL_VARCHAR_MAX_LENGTH)
|
||||
->default(parent::gen_uuid4()),
|
||||
|
||||
(new Rules(MediaModel::NAME->value))
|
||||
->type(Type::STRING)
|
||||
->min(1)
|
||||
->max(parent::MYSQL_VARCHAR_MAX_LENGTH)
|
||||
->default(null),
|
||||
|
||||
(new Rules(MediaModel::TYPE->value))
|
||||
->type(Type::ENUM, MediaTypeEnum::values())
|
||||
->default(null),
|
||||
|
||||
(new Rules(MediaModel::EXTENSION->value))
|
||||
->type(Type::STRING)
|
||||
->min(3)
|
||||
->max(parent::MYSQL_VARCHAR_MAX_LENGTH)
|
||||
->default(null),
|
||||
|
||||
(new Rules(MediaModel::MIME->value))
|
||||
->type(Type::STRING)
|
||||
->min(3)
|
||||
->max(parent::MYSQL_VARCHAR_MAX_LENGTH)
|
||||
->default(null),
|
||||
|
||||
(new Rules(MediaModel::SRCSET->value))
|
||||
->type(Type::STRING)
|
||||
->min(1)
|
||||
->max(parent::MYSQL_VARCHAR_MAX_LENGTH)
|
||||
->default(null)
|
||||
]);
|
||||
}
|
||||
|
||||
// # Helper methods
|
||||
|
||||
// Returns true if an srcset exists for provided key
|
||||
private static function media_srcset_exists(): bool {
|
||||
// No srcet get parameter has been set
|
||||
if (empty($_POST[MediaModel::SRCSET->value])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the provided srcset exists by calling the srcset endpoint
|
||||
return Call("media/srcset?id={$_POST[MediaModel::SRCSET->value]}", Method::GET)->ok;
|
||||
}
|
||||
|
||||
// # Responses
|
||||
|
||||
// Return 422 Unprocessable Content error if request validation failed
|
||||
private function resp_rules_invalid(): Response {
|
||||
return new Response($this->ruleset->get_errors(), 422);
|
||||
}
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to get work data, please try again later", 503);
|
||||
}
|
||||
|
||||
public function main(): Response {
|
||||
// Bail out if request validation failed
|
||||
if (!$this->ruleset->is_valid()) {
|
||||
return $this->resp_rules_invalid();
|
||||
}
|
||||
|
||||
// Bail out if an srcset doesn't exist
|
||||
if (!self::media_srcset_exists()) {
|
||||
return new Response("No media srcset exists with id '{$_POST[MediaModel::SRCSET->value]}'", 404);
|
||||
}
|
||||
|
||||
$insert = $this->db->for(MediaModel::TABLE)
|
||||
->insert([
|
||||
MediaModel::ID->value => $_POST[MediaModel::ID->value],
|
||||
MediaModel::NAME->value => $_POST[MediaModel::NAME->value],
|
||||
MediaModel::MIME->value => $_POST[MediaModel::MIME->value],
|
||||
// Strip dots from extension string if set
|
||||
MediaModel::EXTENSION->value => $_POST[MediaModel::EXTENSION->value]
|
||||
? str_replace(".", "", $_POST[MediaModel::EXTENSION->value])
|
||||
: null,
|
||||
MediaModel::SRCSET->value => $_POST[MediaModel::SRCSET->value],
|
||||
MediaModel::DATE_TIMESTAMP_CREATED->value => time()
|
||||
]);
|
||||
|
||||
// Return media id if insert was successful
|
||||
return $insert
|
||||
? new Response($_POST[MediaModel::ID->value], 201)
|
||||
: $this->resp_database_error();
|
||||
}
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use VLW\API\Databases\VLWdb\VLWdb;
|
||||
use VLW\API\Databases\VLWdb\Models\Media\MediaModel;
|
||||
use VLW\API\Databases\VLWdb\Models\MediaSrcset\MediaSrcsetModel;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Media.php");
|
||||
require_once Path::root("src/databases/models/MediaSrcset.php");
|
||||
|
||||
class GET_MediaSrcset extends VLWdb {
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->ruleset = new Ruleset(strict: true);
|
||||
|
||||
$this->ruleset->GET([
|
||||
(new Rules(MediaSrcsetModel::ID->value))
|
||||
->required()
|
||||
->type(Type::STRING)
|
||||
->min(1)
|
||||
->max(parent::MYSQL_VARCHAR_MAX_LENGTH)
|
||||
]);
|
||||
}
|
||||
|
||||
// # Helper methods
|
||||
|
||||
// Get metadata for the requested srcset
|
||||
private function get_srcset(): array|false {
|
||||
$srcset = $this->db->for(MediaSrcsetModel::TABLE)
|
||||
->where([MediaSrcsetModel::ID->value => $_GET[MediaSrcsetModel::ID->value]])
|
||||
->select([MediaSrcsetModel::ANCHOR_DEFAULT->value]);
|
||||
|
||||
// Something went wrong retrieving rows from the database
|
||||
if (!parent::is_mysqli_result($srcset)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return assoc array of srcset data if it exists
|
||||
return $srcset->num_rows === 1 ? $srcset->fetch_assoc() : false;
|
||||
}
|
||||
|
||||
// Get all media entities that are part of the requested srcset
|
||||
private function get_srcset_media(): mysqli_result|false {
|
||||
$media = $this->db->for(MediaModel::TABLE)
|
||||
->where([MediaModel::SRCSET->value => $_GET[MediaSrcsetModel::ID->value]])
|
||||
->select([
|
||||
MediaModel::ID->value,
|
||||
MediaModel::TYPE->value,
|
||||
MediaModel::MIME->value,
|
||||
MediaModel::EXTENSION->value
|
||||
]);
|
||||
|
||||
return parent::is_mysqli_result($media) ? $media : false;
|
||||
}
|
||||
|
||||
// # Responses
|
||||
|
||||
// Return 422 Unprocessable Content error if request validation failed
|
||||
private function resp_rules_invalid(): Response {
|
||||
return new Response($this->ruleset->get_errors(), 422);
|
||||
}
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to get work data, please try again later", 503);
|
||||
}
|
||||
|
||||
public function main(): Response {
|
||||
// Bail out if request validation failed
|
||||
if (!$this->ruleset->is_valid()) {
|
||||
return $this->resp_rules_invalid();
|
||||
}
|
||||
|
||||
// Get srcset data
|
||||
$srcset = $this->get_srcset();
|
||||
if (!$srcset) {
|
||||
return new Response("No media srcset exist with id '{$_GET[MediaSrcsetModel::ID->value]}'", 404);
|
||||
}
|
||||
|
||||
$media = $this->get_srcset_media();
|
||||
if (!$media) {
|
||||
return new Response("Failed to fetch srcset media", 500);
|
||||
}
|
||||
|
||||
$media_entities = $media->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
// This is the id of the media entity that is considered the default or "fallback"
|
||||
$srcet_default_media_id = $srcset[MediaSrcsetModel::ANCHOR_DEFAULT->value];
|
||||
|
||||
// Return assoc array of all media entities that are in this srcset
|
||||
return new Response([
|
||||
// Return default media entity separately from the rest of the srcset as an assoc array
|
||||
"default" => array_filter($media_entities, fn(array $entity) => $entity[MediaModel::ID->value] === $srcet_default_media_id)[0],
|
||||
// Return all media that isn't default as array of assoc arrays
|
||||
"srcset" => array_filter($media_entities, fn(array $entity) => $entity[MediaModel::ID->value] !== $srcet_default_media_id)
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
|
||||
use Reflect\Method;
|
||||
use function Reflect\Call;
|
||||
|
||||
use VLW\API\Databases\VLWdb\VLWdb;
|
||||
use VLW\API\Databases\VLWdb\Models\MediaSrcset\MediaSrcsetModel;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Media.php");
|
||||
require_once Path::root("src/databases/models/MediaSrcset.php");
|
||||
|
||||
class POST_MediaSrcset extends VLWdb {
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
// # Responses
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to get work data, please try again later", 503);
|
||||
}
|
||||
|
||||
public function main(): Response {
|
||||
// Generate a random UUID for this srcset
|
||||
$id = parent::gen_uuid4();
|
||||
|
||||
// Ensure an srcset with the generated id doesn't exist, although it shouldn't realistically ever happen
|
||||
$srcset_existing = Call("media/srcset?id={$id}", Method::GET);
|
||||
if ($srcset_existing->code !== 404) {
|
||||
// Wow a UUID4 collision... buy a lottery ticket
|
||||
if ($srcset_existing->code === 200) {
|
||||
return $this->main();
|
||||
}
|
||||
|
||||
// Failed to get srcset
|
||||
return new Response("Something went wrong when checking if the srcset exists", 500);
|
||||
}
|
||||
|
||||
// Create new srcset entity
|
||||
$insert = $this->db->for(MediaSrcsetModel::TABLE)
|
||||
->insert([
|
||||
MediaSrcsetModel::ID->value => $id
|
||||
]);
|
||||
|
||||
// Return created srcset id if successful
|
||||
return $insert
|
||||
? new Response($id, 201)
|
||||
: $this->resp_database_error();
|
||||
}
|
||||
}
|
|
@ -6,20 +6,19 @@
|
|||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use Reflect\Method;
|
||||
use function Reflect\Call;
|
||||
|
||||
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");
|
||||
require_once Path::root("src/databases/models/Messages.php");
|
||||
require_once Path::root("src/databases/models/Messages/Messages.php");
|
||||
|
||||
class POST_Messages extends VLWdb {
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->ruleset = new Ruleset(strict: true);
|
||||
|
||||
$this->ruleset->POST([
|
||||
|
@ -34,46 +33,19 @@
|
|||
->min(1)
|
||||
->max(parent::MYSQL_TEXT_MAX_LENGTH)
|
||||
]);
|
||||
}
|
||||
|
||||
// # Responses
|
||||
|
||||
// Return 422 Unprocessable Content error if request validation failed
|
||||
private function resp_rules_invalid(): Response {
|
||||
return new Response($this->ruleset->get_errors(), 422);
|
||||
}
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to get work data, please try again later", 503);
|
||||
parent::__construct(Databases::VLW, $this->ruleset);
|
||||
}
|
||||
|
||||
public function main(): Response {
|
||||
//return new Response(["hello" => "maybe"], 500);
|
||||
// Use copy of request body as entity
|
||||
$entity = $_POST;
|
||||
|
||||
// Bail out if request validation failed
|
||||
if (!$this->ruleset->is_valid()) {
|
||||
return $this->resp_rules_invalid();
|
||||
}
|
||||
$entity[MessagesModel::ID->value] = parent::gen_uuid4();
|
||||
$entity[MessagesModel::DATE_CREATED->value] = time();
|
||||
|
||||
// Generate UUID for entity
|
||||
$id = parent::gen_uuid4();
|
||||
|
||||
// Attempt to create new entity
|
||||
$insert = $this->db->for(MessagesModel::TABLE)
|
||||
->insert([
|
||||
MessagesModel::ID->value => $id,
|
||||
MessagesModel::EMAIL->value => $_POST["email"],
|
||||
MessagesModel::MESSAGE->value => $_POST["message"],
|
||||
MessagesModel::DATE_TIMESTAMP_CREATED->value => time(),
|
||||
]);
|
||||
|
||||
// Bail out if insert failed
|
||||
if (!$insert) {
|
||||
return $this->resp_database_error();
|
||||
}
|
||||
|
||||
// Return 201 Created and entity id
|
||||
return new Response($id, 201);
|
||||
return $this->db->for(MessagesModel::TABLE)->insert($entity) === true
|
||||
? new Response($entity[MessagesModel::ID->value], 201)
|
||||
: new Response("Failed to create message", 500);
|
||||
}
|
||||
}
|
|
@ -1,223 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use Reflect\Method;
|
||||
use function Reflect\Call;
|
||||
|
||||
use VLW\API\Databases\VLWdb\Models\Work\WorkModel;
|
||||
use VLW\API\Databases\VLWdb\Models\Work\WorkTagsModel;
|
||||
use VLW\API\Databases\VLWdb\Models\Work\WorkTagsNameEnum;
|
||||
use VLW\API\Databases\VLWdb\Models\Work\WorkActionsModel;
|
||||
|
||||
require_once Path::root("src/databases/models/Work.php");
|
||||
require_once Path::root("src/databases/models/WorkTags.php");
|
||||
require_once Path::root("src/databases/models/WorkActions.php");
|
||||
|
||||
// "Virtual" database model for the POST request body since we're not writing to a db directly
|
||||
enum ReleasesPostModel: string {
|
||||
case GITHUB_USER = "user";
|
||||
case GITHUB_REPO = "repo";
|
||||
case GITHUB_TAG = "tag";
|
||||
}
|
||||
|
||||
class POST_Releases {
|
||||
// Base URL of the GitHub API (no tailing slash)
|
||||
const GITHUB_API = "https://api.github.com";
|
||||
|
||||
const REGEX_HANDLE = "/@[\w]+/";
|
||||
const REGEX_URL = "/\b(?:https?):\/\/\S+\b/";
|
||||
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
protected CurlHandle $curl;
|
||||
|
||||
public function __construct() {
|
||||
$this->ruleset = new Ruleset(strict: true);
|
||||
|
||||
$this->ruleset->POST([
|
||||
(new Rules(ReleasesPostModel::GITHUB_USER->value))
|
||||
->required()
|
||||
->type(Type::STRING)
|
||||
->min(1),
|
||||
|
||||
(new Rules(ReleasesPostModel::GITHUB_REPO->value))
|
||||
->required()
|
||||
->type(Type::STRING)
|
||||
->min(1),
|
||||
|
||||
(new Rules(ReleasesPostModel::GITHUB_TAG->value))
|
||||
->required()
|
||||
->type(Type::STRING)
|
||||
->type(Type::NUMBER)
|
||||
->min(1)
|
||||
]);
|
||||
|
||||
$this->curl = curl_init();
|
||||
|
||||
curl_setopt($this->curl, CURLOPT_USERAGENT, $_ENV["github"]["user_agent"]);
|
||||
curl_setopt($this->curl, CURLOPT_HEADER, true);
|
||||
curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($this->curl, CURLOPT_HTTPHEADER, [
|
||||
"Accept" => "application/vnd.github+json",
|
||||
"Authorization" => "token {$_ENV["github"]["api_key"]}",
|
||||
"X-GitHub-Api-Version" => "2022-11-28"
|
||||
]);
|
||||
}
|
||||
|
||||
// # GitHub
|
||||
|
||||
// Generate HTML from a GitHub "auto-generate" release body
|
||||
protected static function gh_auto_release_md_to_html(string $md): string {
|
||||
$output = "";
|
||||
|
||||
// Parse each line of markdown
|
||||
$lines = explode(PHP_EOL, $md);
|
||||
|
||||
foreach ($lines as $i => $line) {
|
||||
// Ignore header line from releases
|
||||
if ($i < 1) continue;
|
||||
|
||||
// Replace all URLs with HTMLAnchor tags, they will be PRs
|
||||
$links = [];
|
||||
preg_match_all(self::REGEX_URL, $line, $links, PREG_UNMATCHED_AS_NULL);
|
||||
foreach ($links as $i => $link) {
|
||||
if (empty($link)) continue;
|
||||
|
||||
// Last crumb from link pathname will be the PR id
|
||||
$pr_id = explode("/", $link[$i]);
|
||||
$pr_id = end($pr_id);
|
||||
|
||||
$line = str_replace($link, "<a href='{$link[$i]}'>{$pr_id}</a>", $line);
|
||||
}
|
||||
|
||||
// Replace all at-handles with links to GitHub user profiles
|
||||
$handles = [];
|
||||
preg_match_all(self::REGEX_HANDLE, $line, $handles, PREG_UNMATCHED_AS_NULL);
|
||||
foreach ($handles as $i => $handle) {
|
||||
if (empty($handle)) continue;
|
||||
|
||||
// GitHub user URL without the "@"
|
||||
$url = "https://github.com/" . substr($handle[$i], 1);
|
||||
|
||||
$line = str_replace($handle, "<a href='{$url}'>{$handle[$i]}</a>", $line);
|
||||
}
|
||||
|
||||
$output .= "<p>{$line}</p>";
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
// Return fully qualified URL to GitHub API releases endpoint
|
||||
private static function get_url(): string {
|
||||
return implode("/", [
|
||||
self::GITHUB_API,
|
||||
"repos",
|
||||
$_POST[ReleasesPostModel::GITHUB_USER->value],
|
||||
$_POST[ReleasesPostModel::GITHUB_REPO->value],
|
||||
"releases",
|
||||
"tags",
|
||||
$_POST[ReleasesPostModel::GITHUB_TAG->value],
|
||||
]);
|
||||
}
|
||||
|
||||
// Fetch release information from GitHub API
|
||||
private function fetch_release_data(): array {
|
||||
$url = self::get_url();
|
||||
curl_setopt($this->curl, CURLOPT_URL, self::get_url());
|
||||
|
||||
$resp = curl_exec($this->curl);
|
||||
|
||||
$header_size = curl_getinfo($this->curl, CURLINFO_HEADER_SIZE);
|
||||
$header = substr($resp, 0, $header_size);
|
||||
$body = substr($resp, $header_size);
|
||||
|
||||
return json_decode($body, true);
|
||||
}
|
||||
|
||||
// # Sup
|
||||
|
||||
private function create_link_to_release_page(string $id, string $href): Response {
|
||||
return Call("work/actions?id={$id}", Method::POST, [
|
||||
WorkActionsModel::DISPLAY_TEXT->value => "Release details",
|
||||
WorkActionsModel::HREF->value => $href,
|
||||
WorkActionsModel::EXTERNAL->value => true
|
||||
]);
|
||||
}
|
||||
|
||||
// Create a tag for entity
|
||||
private function create_tag(string $id, WorkTagsNameEnum $tag): Response {
|
||||
return Call("work/tags?id={$id}", Method::POST, [
|
||||
// Set "RELEASE" tag on new entity
|
||||
WorkTagsModel::NAME->value => $tag->value
|
||||
]);
|
||||
}
|
||||
|
||||
// # Responses
|
||||
|
||||
// Return 422 Unprocessable Content error if request validation failed
|
||||
private function resp_rules_invalid(): Response {
|
||||
return new Response($this->ruleset->get_errors(), 422);
|
||||
}
|
||||
|
||||
public function main(): Response {
|
||||
// Bail out if request validation failed
|
||||
if (!$this->ruleset->is_valid()) {
|
||||
return $this->resp_rules_invalid();
|
||||
}
|
||||
|
||||
$data = $this->fetch_release_data();
|
||||
if (!$data) {
|
||||
return new Response("Failed to fetch release data", 500);
|
||||
}
|
||||
|
||||
|
||||
// Transform repo name to lowercase for summary title
|
||||
$title = strtolower($_POST["repo"]);
|
||||
|
||||
// Use repo name and tag name as heading for summary
|
||||
$summary = "<h3>Release {$title}@{$data["name"]}</h3>";
|
||||
// Append HTML-ified release notes from GitHub to summary
|
||||
$summary .= self::gh_auto_release_md_to_html($data["body"]);
|
||||
|
||||
$date_published = new \DateTime($data["published_at"], new \DateTimeZone("UTC"));
|
||||
|
||||
// Create work entity
|
||||
$work_entity = Call("work", Method::POST, [
|
||||
WorkModel::SUMMARY->value => $summary,
|
||||
// Convert time created to Unix timestamp for work endpoint
|
||||
WorkModel::DATE_TIMESTAMP_CREATED->value => $date_published->format("U"),
|
||||
]);
|
||||
|
||||
// Bail out if creating the work entity failed
|
||||
if (!$work_entity->ok) {
|
||||
return new Response("Failed to create work entity for release", 500);
|
||||
}
|
||||
|
||||
$work_entity_id = $work_entity->output();
|
||||
|
||||
// Create entity tags for release
|
||||
$tags = [
|
||||
WorkTagsNameEnum::VLW,
|
||||
WorkTagsNameEnum::RELEASE
|
||||
];
|
||||
foreach ($tags as $tag) {
|
||||
// Create entity tag for release or exit if failed to create
|
||||
if (!$this->create_tag($work_entity_id, $tag)->ok) {
|
||||
return new Response("Failed to create {$tag->name} tag for release entity", 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Create link to release page on GitHub
|
||||
if (!$this->create_link_to_release_page($work_entity_id, $data["html_url"])) {
|
||||
return new Response("Failed to create link to release page on GitHub", 500);
|
||||
}
|
||||
|
||||
return new Response($work_entity_id, 201);
|
||||
}
|
||||
}
|
|
@ -9,13 +9,15 @@
|
|||
|
||||
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;
|
||||
use VLW\API\Databases\VLWdb\Models\Work\WorkActionsModel;
|
||||
|
||||
require_once Path::root("src/Endpoints.php");
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Work.php");
|
||||
require_once Path::root("src/databases/models/WorkActions.php");
|
||||
require_once Path::root("src/databases/models/Work/Work.php");
|
||||
|
||||
class GET_Search extends VLWdb {
|
||||
const GET_QUERY = "q";
|
||||
|
@ -23,118 +25,35 @@
|
|||
protected Ruleset $ruleset;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->ruleset = new Ruleset(strict: true);
|
||||
|
||||
$this->ruleset->GET([
|
||||
(new Rules(self::GET_QUERY))
|
||||
->required()
|
||||
->type(Type::STRING)
|
||||
->min(2)
|
||||
->min(1)
|
||||
->max(parent::MYSQL_VARCHAR_MAX_LENGTH)
|
||||
]);
|
||||
|
||||
parent::__construct(Databases::VLW, $this->ruleset);
|
||||
}
|
||||
|
||||
// Return an SQL string from array for use in prepared statements
|
||||
private static function array_to_wildcard_sql(array $columns): string {
|
||||
$sql = array_map(fn(string $column): string => "{$column} LIKE CONCAT('%', ?, '%')", $columns);
|
||||
|
||||
return implode(" OR ", $sql);
|
||||
}
|
||||
|
||||
// Return chained AND statements from array for use in prepared statements
|
||||
private static function array_to_and_statement(array $keys): string {
|
||||
$sql = array_map(fn(string $k): string => "{$k} = ?", $keys);
|
||||
|
||||
return implode(" AND ", $sql);
|
||||
}
|
||||
|
||||
// Wildcard search columns in table with query string from query string
|
||||
// This has to be implemented manually until "libmysqldriver/MySQL" supports wildcard SELECT
|
||||
private function search(string $table, array $columns, array $conditions = null): array {
|
||||
// Create CSV from columns array
|
||||
$columns_concat = implode(",", $columns);
|
||||
|
||||
// Create SQL LIKE wildcard statement for each column.
|
||||
$where = self::array_to_wildcard_sql($columns);
|
||||
|
||||
// Create array of values from query string for each colum
|
||||
$values = array_fill(0, count($columns), $_GET[self::GET_QUERY]);
|
||||
|
||||
if ($conditions) {
|
||||
$conditions_sql = self::array_to_and_statement(array_keys($conditions));
|
||||
|
||||
// Wrap positive where statements and prepare new group of conditions
|
||||
// WHERE (<search_terms>) AND (<conditions>)
|
||||
$where = "({$where}) AND ({$conditions_sql})";
|
||||
|
||||
// Append values from conditions statements to prepared statement
|
||||
array_push($values, ...array_values($conditions));
|
||||
}
|
||||
|
||||
// Order the rows by the array index of $colums received
|
||||
$rows = $this->db->exec("SELECT {$columns_concat} FROM {$table} WHERE {$where} ORDER BY {$columns_concat}", $values);
|
||||
// Return results as assoc or empty array
|
||||
return parent::is_mysqli_result($rows) ? $rows->fetch_all(MYSQLI_ASSOC) : [];
|
||||
}
|
||||
|
||||
// Search work table
|
||||
private function search_work(): array {
|
||||
$search = [
|
||||
WorkModel::TITLE->value,
|
||||
WorkModel::SUMMARY->value,
|
||||
WorkModel::DATE_TIMESTAMP_CREATED->value,
|
||||
WorkModel::ID->value
|
||||
];
|
||||
|
||||
$conditions = [
|
||||
WorkModel::IS_LISTABLE->value => true
|
||||
];
|
||||
|
||||
$results = $this->search(WorkModel::TABLE, $search, $conditions);
|
||||
|
||||
foreach ($results as &$result) {
|
||||
$result["actions"] = (new Call(Endpoints::WORK_ACTIONS->value))
|
||||
->params([WorkActionsModel::ANCHOR->value => $result[WorkModel::ID->value]])
|
||||
->get()->output();
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
// # Responses
|
||||
|
||||
// Return 422 Unprocessable Content error if request validation failed
|
||||
private function resp_rules_invalid(): Response {
|
||||
return new Response($this->ruleset->get_errors(), 422);
|
||||
}
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to get work data, please try again later", 503);
|
||||
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 {
|
||||
// Bail out if request validation failed
|
||||
if (!$this->ruleset->is_valid()) {
|
||||
return $this->resp_rules_invalid();
|
||||
}
|
||||
|
||||
// Get search results for each category
|
||||
$categories = [
|
||||
WorkModel::TABLE => $this->search_work()
|
||||
$results = [
|
||||
Endpoints::WORK->value => $this->search_work()->output()
|
||||
];
|
||||
|
||||
// Count total number of results from all categories
|
||||
$total_num_results = 0;
|
||||
foreach (array_values($categories) as $results) {
|
||||
$total_num_results += count($results);
|
||||
}
|
||||
// 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 new Response([
|
||||
"query" => $_GET[self::GET_QUERY],
|
||||
"results" => $categories,
|
||||
"total_num_results" => $total_num_results
|
||||
]);
|
||||
// Return 404 if no search results
|
||||
return new Response($results, $num_results > 0 ? 200 : 404);
|
||||
}
|
||||
}
|
|
@ -1,15 +1,19 @@
|
|||
<?php
|
||||
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use VLW\API\Databases\VLWdb\VLWdb;
|
||||
use const VLW\API\RESP_DELETE_OK;
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Work\WorkModel;
|
||||
|
||||
require_once Path::root("src/Endpoints.php");
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Work.php");
|
||||
|
||||
|
@ -17,44 +21,47 @@
|
|||
protected Ruleset $ruleset;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->ruleset = new Ruleset(strict: true);
|
||||
|
||||
$this->ruleset->GET([
|
||||
(new Rules("id"))
|
||||
->required()
|
||||
$this->ruleset->POST([
|
||||
(new Rules(WorkModel::ID->value))
|
||||
->type(Type::STRING)
|
||||
->min(1)
|
||||
->max(parent::MYSQL_VARCHAR_MAX_LENGTH)
|
||||
->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_LISTABLE->value))
|
||||
->type(Type::BOOLEAN),
|
||||
|
||||
(new Rules(WorkModel::IS_READABLE->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)
|
||||
]);
|
||||
}
|
||||
|
||||
// # Responses
|
||||
|
||||
// Return 422 Unprocessable Content error if request validation failed
|
||||
private function resp_rules_invalid(): Response {
|
||||
return new Response($this->ruleset->get_errors(), 422);
|
||||
}
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to delete work data, please try again later", 503);
|
||||
parent::__construct(Databases::VLW, $this->ruleset);
|
||||
}
|
||||
|
||||
public function main(): Response {
|
||||
// Bail out if request validation failed
|
||||
if (!$this->ruleset->is_valid()) {
|
||||
return $this->resp_rules_invalid();
|
||||
}
|
||||
|
||||
// Attempt to update the entity
|
||||
$update = $this->db->for(WorkModel::TABLE)
|
||||
->where([WorkModel::ID->value => $_GET["id"]])
|
||||
->update([
|
||||
WorkModel::IS_LISTABLE->value => false,
|
||||
WorkModel::IS_READABLE->value => false
|
||||
]);
|
||||
|
||||
return $update ? new Response($_GET["id"]) : $this->resp_database_error();
|
||||
return $this->db->for(FieldsEnumsModel::TABLE)->delete($_POST) === true
|
||||
? new Response(RESP_DELETE_OK)
|
||||
: new Response("Failed to delete work entity", 500);
|
||||
}
|
||||
}
|
|
@ -1,136 +1,98 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Call;
|
||||
use Reflect\Path;
|
||||
use Reflect\Method;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
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;
|
||||
use VLW\API\Databases\VLWdb\Models\Work\WorkTagsModel;
|
||||
use VLW\API\Databases\VLWdb\Models\Work\WorkActionsModel;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Work.php");
|
||||
require_once Path::root("src/databases/models/WorkTags.php");
|
||||
require_once Path::root("src/databases/models/WorkActions.php");
|
||||
require_once Path::root("src/databases/models/Work/Work.php");
|
||||
|
||||
class GET_Work extends VLWdb {
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->ruleset = new Ruleset(strict: true);
|
||||
|
||||
$this->ruleset->GET([
|
||||
(new Rules("id"))
|
||||
(new Rules(WorkModel::ID->value))
|
||||
->type(Type::STRING)
|
||||
->min(1)
|
||||
->max(parent::MYSQL_VARCHAR_MAX_LENGTH)
|
||||
->default(null)
|
||||
->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_LISTABLE->value))
|
||||
->type(Type::BOOLEAN)
|
||||
->default(true),
|
||||
|
||||
(new Rules(WorkModel::IS_READABLE->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)
|
||||
]);
|
||||
}
|
||||
|
||||
// # Helper methods
|
||||
|
||||
private function fetch_row_tags(string $id): array {
|
||||
$resp = $this->db->for(WorkTagsModel::TABLE)
|
||||
->where([WorkTagsModel::ANCHOR->value => $id])
|
||||
->select(WorkTagsModel::NAME->value);
|
||||
|
||||
return parent::is_mysqli_result($resp) ? $resp->fetch_all(MYSQLI_ASSOC) : [];
|
||||
}
|
||||
|
||||
// # Responses
|
||||
|
||||
// Return 422 Unprocessable Content error if request validation failed
|
||||
private function resp_rules_invalid(): Response {
|
||||
return new Response($this->ruleset->get_errors(), 422);
|
||||
}
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to get work data, please try again later", 503);
|
||||
}
|
||||
|
||||
private function resp_item_details(string $id): Response {
|
||||
$resp = $this->db->for(WorkModel::TABLE)
|
||||
->where([
|
||||
WorkModel::ID->value => $id,
|
||||
WorkModel::IS_READABLE->value => true
|
||||
])
|
||||
->limit(1)
|
||||
->select([
|
||||
WorkModel::ID->value,
|
||||
WorkModel::TITLE->value,
|
||||
WorkModel::SUMMARY->value,
|
||||
WorkModel::COVER_SRCSET->value,
|
||||
WorkModel::DATE_YEAR->value,
|
||||
WorkModel::DATE_MONTH->value,
|
||||
WorkModel::DATE_DAY->value,
|
||||
WorkModel::DATE_TIMESTAMP_MODIFIED->value,
|
||||
WorkModel::DATE_TIMESTAMP_CREATED->value
|
||||
]);
|
||||
|
||||
// Bail out if something went wrong retrieving rows from the database
|
||||
if (!parent::is_mysqli_result($resp)) {
|
||||
return $this->resp_database_error();
|
||||
}
|
||||
|
||||
return $resp->num_rows === 1
|
||||
? new Response($resp->fetch_assoc())
|
||||
: new Response("No entity with id '{$id}' was found", 404);
|
||||
parent::__construct(Databases::VLW, $this->ruleset);
|
||||
}
|
||||
|
||||
public function main(): Response {
|
||||
// Bail out if request validation failed
|
||||
if (!$this->ruleset->is_valid()) {
|
||||
return $this->resp_rules_invalid();
|
||||
// Use copy of search paramters as filters
|
||||
$filters = $_GET;
|
||||
|
||||
// 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]}%"
|
||||
];
|
||||
}
|
||||
|
||||
// Return details about a specific item by id
|
||||
if (!empty($_GET["id"])) {
|
||||
return $this->resp_item_details($_GET["id"]);
|
||||
// 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]}%"
|
||||
];
|
||||
}
|
||||
|
||||
$resp = $this->db->for(WorkModel::TABLE)
|
||||
->where([WorkModel::IS_LISTABLE->value => true])
|
||||
->order([WorkModel::DATE_TIMESTAMP_CREATED->value => "DESC"])
|
||||
$response = $this->db->for(WorkModel::TABLE)
|
||||
->where($filters)
|
||||
->order([WorkModel::DATE_CREATED->value => "DESC"])
|
||||
->select([
|
||||
WorkModel::ID->value,
|
||||
WorkModel::TITLE->value,
|
||||
WorkModel::SUMMARY->value,
|
||||
WorkModel::COVER_SRCSET->value,
|
||||
WorkModel::IS_LISTABLE->value,
|
||||
WorkModel::IS_READABLE->value,
|
||||
WorkModel::DATE_YEAR->value,
|
||||
WorkModel::DATE_MONTH->value,
|
||||
WorkModel::DATE_DAY->value,
|
||||
WorkModel::DATE_TIMESTAMP_MODIFIED->value,
|
||||
WorkModel::DATE_TIMESTAMP_CREATED->value
|
||||
WorkModel::DATE_MODIFIED->value,
|
||||
WorkModel::DATE_CREATED->value
|
||||
]);
|
||||
|
||||
// Bail out if something went wrong retrieving rows from the database
|
||||
if (!parent::is_mysqli_result($resp)) {
|
||||
return $this->resp_database_error();
|
||||
}
|
||||
|
||||
// Resolve foreign keys
|
||||
$rows = [];
|
||||
while ($row = $resp->fetch_assoc()) {
|
||||
$row["tags"] = $this->fetch_row_tags($row["id"]);
|
||||
|
||||
// Fetch actions for work entity by id from endpoint
|
||||
$row["actions"] = (new Call(Endpoints::WORK_ACTIONS->value))
|
||||
->params([WorkActionsModel::ANCHOR->value => $row[WorkModel::ID->value]])
|
||||
->get()->output();
|
||||
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
return new Response($rows);
|
||||
return $response->num_rows > 0
|
||||
? new Response($response->fetch_all(MYSQLI_ASSOC))
|
||||
: new Response([], 404);
|
||||
}
|
||||
}
|
|
@ -1,30 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Call;
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use Reflect\Method;
|
||||
use function Reflect\Call;
|
||||
|
||||
use VLW\API\Databases\VLWdb\VLWdb;
|
||||
use VLW\API\Databases\VLWdb\Models\Work\WorkModel;
|
||||
use VLW\API\Databases\VLWdb\Models\WorkPermalinks\WorkPermalinksModel;
|
||||
use VLW\API\Endpoints;
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Work\{
|
||||
WorkModel,
|
||||
WorkPermalinksModel
|
||||
};
|
||||
|
||||
require_once Path::root("src/Endpoints.php");
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Work.php");
|
||||
require_once Path::root("src/databases/models/WorkPermalinks.php");
|
||||
require_once Path::root("src/databases/models/Work/Work.php");
|
||||
require_once Path::root("src/databases/models/Work/WorkPermalinks.php");
|
||||
|
||||
class PATCH_Work extends VLWdb {
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
protected Response $current_entity;
|
||||
protected array $updated_entity;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->ruleset = new Ruleset(strict: true);
|
||||
|
||||
$this->ruleset->GET([
|
||||
|
@ -52,19 +53,19 @@
|
|||
(new Rules(WorkModel::IS_READABLE->value))
|
||||
->type(Type::BOOLEAN),
|
||||
|
||||
(new Rules(WorkModel::DATE_TIMESTAMP_CREATED->value))
|
||||
(new Rules(WorkModel::DATE_MODIFIED->value))
|
||||
->type(Type::NUMBER)
|
||||
->min(0)
|
||||
->max(parent::MYSQL_INT_MAX_LENGHT)
|
||||
->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)
|
||||
]);
|
||||
|
||||
$this->get_existing_entity();
|
||||
|
||||
// Copy all provided post data into a new array
|
||||
$this->updated_entity = $_POST;
|
||||
|
||||
// Set date modified timestamp
|
||||
$this->updated_entity[WorkModel::DATE_TIMESTAMP_MODIFIED->value] = time();
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
// Generate a slug URL from string
|
||||
|
@ -72,130 +73,46 @@
|
|||
return strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $input)));
|
||||
}
|
||||
|
||||
// # Helper methods
|
||||
|
||||
private function get_existing_entity(): Response {
|
||||
// Check if an entity already exists with slugified title from GET endpoint
|
||||
$this->current_entity = Call("work?id={$_GET["id"]}", Method::GET);
|
||||
|
||||
// Response is not 404 (Not found) so we can't create the entity
|
||||
if ($this->current_entity->code !== 200) {
|
||||
// Response is not a valid entity, something went wrong
|
||||
if ($this->current_entity->code !== 404) {
|
||||
return $this->resp_database_error();
|
||||
}
|
||||
|
||||
// Return 402 Conflict
|
||||
return new Response("No entity with id '{$_GET["id"]}' was found", 404);
|
||||
}
|
||||
|
||||
return $this->current_entity;
|
||||
// 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])
|
||||
];
|
||||
}
|
||||
|
||||
// Create new permalink for entity slug
|
||||
private function create_permalink(string $slug): bool {
|
||||
$create = Call("work/permalinks", Method::POST, [
|
||||
WorkPermalinksModel::SLUG->value => $slug,
|
||||
WorkPermalinksModel::ANCHOR->value => $slug
|
||||
]);
|
||||
|
||||
return $create->ok;
|
||||
}
|
||||
|
||||
// ## Updated entity
|
||||
|
||||
private function change_slug(): bool {
|
||||
if (!array_key_exists(WorkModel::ID->value, $this->updated_entity)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Generate new permalink for entity id
|
||||
return $this->create_permalink($this->updated_entity[WorkModel::ID->value]);
|
||||
}
|
||||
|
||||
private function timestamp_to_dates(): void {
|
||||
if (!array_key_exists(WorkModel::DATE_TIMESTAMP_CREATED->value, $this->updated_entity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get timestamp from post data
|
||||
$timestamp = $this->updated_entity[WorkModel::DATE_TIMESTAMP_CREATED->value];
|
||||
|
||||
// Update fractured dates from timestamp
|
||||
$this->updated_entity[WorkModel::DATE_YEAR->value] = date("Y", $timestamp);
|
||||
$this->updated_entity[WorkModel::DATE_MONTH ->value] = date("n", $timestamp);
|
||||
$this->updated_entity[WorkModel::DATE_DAY->value] = date("j", $timestamp);
|
||||
}
|
||||
|
||||
// # Responses
|
||||
|
||||
// Return 422 Unprocessable Content error if request validation failed
|
||||
private function resp_rules_invalid(): Response {
|
||||
return new Response($this->ruleset->get_errors(), 422);
|
||||
}
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to get work data, please try again later", 503);
|
||||
}
|
||||
|
||||
// Return a 422 Unprocessable Entity if there is nothing to change
|
||||
private function resp_no_changes(): Response {
|
||||
return new Response("No columns to update", 422);
|
||||
}
|
||||
|
||||
// Rollback changes and return error response
|
||||
private function resp_permalink_error_rollback(): Response {
|
||||
$update = $this->db->for(WorkModel::TABLE)
|
||||
->where([WorkModel::ID->value => $_GET["id"]])
|
||||
->update($this->current_entity->output());
|
||||
|
||||
return $update
|
||||
? new Response("Failed to create new permalink for updated entity. Changes have been rolled back", 500)
|
||||
: new Reponse("Failed to create new permalink for updated entity. Changes failed to rollback, this is bad.", 500);
|
||||
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 {
|
||||
// Bail out if request validation failed
|
||||
if (!$this->ruleset->is_valid()) {
|
||||
return $this->resp_rules_invalid();
|
||||
}
|
||||
// Use copy of request body as entity
|
||||
$entity = $_POST;
|
||||
|
||||
// Empty payload, nothing to do
|
||||
if (empty($_POST)) {
|
||||
return $this->resp_no_changes();
|
||||
}
|
||||
// Generate a new slug id from title if changed
|
||||
if ($_POST[WorkModel::TITLE->value]) {
|
||||
$slug = $_POST[WorkModel::TITLE->value];
|
||||
|
||||
// Generate new slug for entity if title is updated
|
||||
if (array_key_exists(WorkModel::TITLE->value, $_POST)) {
|
||||
// Generate URL slug from title text or UUID if undefined
|
||||
$slug = self::gen_slug($_POST["title"]);
|
||||
|
||||
// Save generated slug from title if it's different from existing slug
|
||||
if ($slug !== $this->current_entity->output()[WorkModel::ID->value]) {
|
||||
$this->updated_entity[WorkModel::ID->value] = $slug;
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Update fractured dates from timestamp
|
||||
$this->timestamp_to_dates();
|
||||
|
||||
// Attempt to update the entity
|
||||
$update = $this->db->for(WorkModel::TABLE)
|
||||
->where([WorkModel::ID->value => $_GET["id"]])
|
||||
->update($this->updated_entity);
|
||||
|
||||
// Bail out if update failed
|
||||
if (!$update) {
|
||||
return $this->resp_database_error();
|
||||
// Generate new work date fields from timestamp
|
||||
if ($_POST[WorkModel::DATE_CREATED->value]) {
|
||||
array_merge($entity, self::gen_date_created());
|
||||
}
|
||||
|
||||
// Create new slug for entity if title was changed
|
||||
if (!$this->change_slug()) {
|
||||
return $this->resp_permalink_error_rollback();
|
||||
}
|
||||
|
||||
// Return 200 OK and new or existing entity slug as body
|
||||
return new Response($this->current_entity->output()[WorkModel::ID->value]);
|
||||
// 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);
|
||||
}
|
||||
}
|
|
@ -1,27 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Call;
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use Reflect\Method;
|
||||
use function Reflect\Call;
|
||||
|
||||
use VLW\API\Databases\VLWdb\VLWdb;
|
||||
use VLW\API\Databases\VLWdb\Models\Work\WorkModel;
|
||||
use VLW\API\Databases\VLWdb\Models\WorkPermalinks\WorkPermalinksModel;
|
||||
use VLW\API\Endpoints;
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Work\{
|
||||
WorkModel,
|
||||
WorkPermalinksModel
|
||||
};
|
||||
|
||||
require_once Path::root("src/Endpoints.php");
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Work.php");
|
||||
require_once Path::root("src/databases/models/WorkPermalinks.php");
|
||||
require_once Path::root("src/databases/models/Work/Work.php");
|
||||
require_once Path::root("src/databases/models/Work/WorkPermalinks.php");
|
||||
|
||||
class POST_Work extends VLWdb {
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->ruleset = new Ruleset(strict: true);
|
||||
|
||||
$this->ruleset->POST([
|
||||
|
@ -37,12 +41,22 @@
|
|||
->max(parent::MYSQL_TEXT_MAX_LENGTH)
|
||||
->default(null),
|
||||
|
||||
(new Rules(WorkModel::DATE_TIMESTAMP_CREATED->value))
|
||||
(new Rules(WorkModel::IS_LISTABLE->value))
|
||||
->type(Type::BOOLEAN)
|
||||
->default(false),
|
||||
|
||||
(new Rules(WorkModel::IS_READABLE->value))
|
||||
->type(Type::BOOLEAN)
|
||||
->default(false),
|
||||
|
||||
(new Rules(WorkModel::DATE_CREATED->value))
|
||||
->type(Type::NUMBER)
|
||||
->min(1)
|
||||
->max(parent::MYSQL_INT_MAX_LENGHT)
|
||||
->default(null)
|
||||
->max(parent::MYSQL_INT_MAX_LENGTH)
|
||||
->default(time())
|
||||
]);
|
||||
|
||||
parent::__construct(Databases::VLW, $this->ruleset);
|
||||
}
|
||||
|
||||
// Generate a slug URL from string
|
||||
|
@ -50,84 +64,51 @@
|
|||
return strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $input)));
|
||||
}
|
||||
|
||||
// Create permalink for entity slug
|
||||
private function create_permalink(string $slug): bool {
|
||||
$create = Call("work/permalinks", Method::POST, [
|
||||
WorkPermalinksModel::SLUG->value => $slug,
|
||||
WorkPermalinksModel::ANCHOR->value => $slug
|
||||
]);
|
||||
// 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 $create->ok;
|
||||
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)
|
||||
];
|
||||
}
|
||||
|
||||
// # Responses
|
||||
|
||||
// Return 422 Unprocessable Content error if request validation failed
|
||||
private function resp_rules_invalid(): Response {
|
||||
return new Response($this->ruleset->get_errors(), 422);
|
||||
}
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to get work data, please try again later", 503);
|
||||
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 {
|
||||
// Bail out if request validation failed
|
||||
if (!$this->ruleset->is_valid()) {
|
||||
return $this->resp_rules_invalid();
|
||||
}
|
||||
// Use copy of request body as entity
|
||||
$entity = $_POST;
|
||||
|
||||
// Generate URL slug from title text or UUID if undefined
|
||||
$slug = !empty($_POST["title"]) ? self::gen_slug($_POST["title"]) : parent::gen_uuid4();
|
||||
$entity[WorkModel::ID->value] = $_POST[WorkModel::TITLE->value]
|
||||
? self::gen_slug($_POST[WorkModel::TITLE->value])
|
||||
: parent::gen_uuid4();
|
||||
|
||||
// Check if an entity already exists with slugified title from GET endpoint
|
||||
$existing_entity = Call("work?id={$slug}", Method::GET);
|
||||
// Response is not 404 (Not found) so we can't create the entity
|
||||
if ($existing_entity->code !== 404) {
|
||||
// Response is not a valid entity, something went wrong
|
||||
if ($existing_entity->code !== 200) {
|
||||
return $this->resp_database_error();
|
||||
}
|
||||
|
||||
// Return 402 Conflict
|
||||
return new Response("Entity with id '{$slug}' already exists", 402);
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Get created timestamp from payload or use current time if not specified
|
||||
$created_timestamp = $_POST[WorkModel::DATE_TIMESTAMP_CREATED->value]
|
||||
? $_POST[WorkModel::DATE_TIMESTAMP_CREATED->value]
|
||||
: time();
|
||||
// Generate the necessary date fields
|
||||
array_merge($entity, self::gen_date_created());
|
||||
|
||||
// Attempt to create new entity
|
||||
$insert = $this->db->for(WorkModel::TABLE)
|
||||
->insert([
|
||||
WorkModel::ID->value => $slug,
|
||||
WorkModel::TITLE->value => $_POST["title"],
|
||||
WorkModel::SUMMARY->value => $_POST["summary"],
|
||||
WorkModel::IS_LISTABLE->value => true,
|
||||
WorkModel::IS_READABLE->value => true,
|
||||
WorkModel::DATE_YEAR->value => date("Y", $created_timestamp),
|
||||
WorkModel::DATE_MONTH ->value => date("n", $created_timestamp),
|
||||
WorkModel::DATE_DAY->value => date("j", $created_timestamp),
|
||||
WorkModel::DATE_TIMESTAMP_MODIFIED->value => null,
|
||||
WorkModel::DATE_TIMESTAMP_CREATED->value => $created_timestamp,
|
||||
]);
|
||||
|
||||
// Bail out if insert failed
|
||||
if (!$insert) {
|
||||
return $this->resp_database_error();
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Create permalink for new entity
|
||||
if (!$this->create_permalink($slug)) {
|
||||
// Rollback created entity if permalink creation failed
|
||||
Call("work", Method::DELETE, [WorkModel::ID->value => $slug]);
|
||||
|
||||
return new Response("Failed to create permalink", 500);
|
||||
}
|
||||
|
||||
// Return 201 Created and entity slug as body
|
||||
return new Response($slug, 201);
|
||||
// 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()
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -6,10 +6,11 @@
|
|||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use Reflect\Method;
|
||||
use function Reflect\Call;
|
||||
|
||||
use VLW\API\Databases\VLWdb\VLWdb;
|
||||
use const VLW\API\RESP_DELETE_OK;
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Work\WorkActionsModel;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
|
@ -23,51 +24,15 @@
|
|||
$this->ruleset = new Ruleset(strict: true);
|
||||
|
||||
$this->ruleset->POST([
|
||||
(new Rules("id"))
|
||||
->required()
|
||||
->type(Type::STRING)
|
||||
(new Rules(WorkActionsModel::REF_WORK_ID->value))
|
||||
->min(1)
|
||||
->max(parent::MYSQL_VARCHAR_MAX_LENGTH)
|
||||
]);
|
||||
}
|
||||
|
||||
// # Responses
|
||||
|
||||
// Return 422 Unprocessable Content error if request validation failed
|
||||
private function resp_rules_invalid(): Response {
|
||||
return new Response($this->ruleset->get_errors(), 422);
|
||||
}
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to get work data, please try again later", 503);
|
||||
}
|
||||
|
||||
public function main(): Response {
|
||||
// Bail out if request validation failed
|
||||
if (!$this->ruleset->is_valid()) {
|
||||
return $this->resp_rules_invalid();
|
||||
}
|
||||
|
||||
// Ensure the action exists by id
|
||||
$existing_action = $this->db->for(WorkActionsModel::TABLE)
|
||||
->where([
|
||||
WorkActionsModel::ID->value => $_POST["id"]
|
||||
])
|
||||
->select(null);
|
||||
|
||||
// Return idempotent deletion if the action does not exist
|
||||
if ($existing_action->num_rows === 0) {
|
||||
return new Response($_POST["id"]);
|
||||
}
|
||||
|
||||
// Attempt to delete action by id
|
||||
$delete = $this->db->for(WorkActionsModel::TABLE)
|
||||
->delete([
|
||||
WorkActionsModel::ID->value => $_POST["id"]
|
||||
]);
|
||||
|
||||
// Return 201 Created and entity id as body if insert was successful
|
||||
return $delete === true ? new Response($_POST["id"], 201) : $this->resp_database_error();
|
||||
return $this->db->for(WorkActionsModel::TABLE)->delete($_POST) === true
|
||||
? new Response(RESP_DELETE_OK)
|
||||
: new Response("Failed to delete action for work entity", 500);
|
||||
}
|
||||
}
|
|
@ -6,62 +6,44 @@
|
|||
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");
|
||||
require_once Path::root("src/databases/models/WorkActions.php");
|
||||
require_once Path::root("src/databases/models/Work/WorkActions.php");
|
||||
|
||||
class GET_WorkActions extends VLWdb {
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->ruleset = new Ruleset(strict: true);
|
||||
|
||||
$this->ruleset->GET([
|
||||
(new Rules(WorkActionsModel::ANCHOR->value))
|
||||
->required()
|
||||
(new Rules(WorkActionsModel::REF_WORK_ID->value))
|
||||
->type(Type::STRING)
|
||||
->min(1)
|
||||
->max(parent::MYSQL_VARCHAR_MAX_LENGTH)
|
||||
]);
|
||||
}
|
||||
|
||||
// # Responses
|
||||
|
||||
// Return 422 Unprocessable Content error if request validation failed
|
||||
private function resp_rules_invalid(): Response {
|
||||
return new Response($this->ruleset->get_errors(), 422);
|
||||
}
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to get work data, please try again later", 503);
|
||||
parent::__construct(Databases::VLW, $this->ruleset);
|
||||
}
|
||||
|
||||
public function main(): Response {
|
||||
// Bail out if request validation failed
|
||||
if (!$this->ruleset->is_valid()) {
|
||||
return $this->resp_rules_invalid();
|
||||
}
|
||||
|
||||
$resp = $this->db->for(WorkActionsModel::TABLE)
|
||||
->where([WorkActionsModel::ANCHOR->value => $_GET[WorkActionsModel::ANCHOR->value]])
|
||||
$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,
|
||||
WorkActionsModel::EXTERNAL->value
|
||||
]);
|
||||
|
||||
// Bail out if something went wrong retrieving rows from the database
|
||||
if (!parent::is_mysqli_result($resp)) {
|
||||
return $this->resp_database_error();
|
||||
}
|
||||
|
||||
return $resp->num_rows > 0
|
||||
? new Response($resp->fetch_all(MYSQLI_ASSOC))
|
||||
: new Response([]);
|
||||
return $response->num_rows > 0
|
||||
? new Response($response->fetch_all(MYSQLI_ASSOC))
|
||||
: new Response([], 404);
|
||||
}
|
||||
}
|
|
@ -1,36 +1,39 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Call;
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use Reflect\Method;
|
||||
use function Reflect\Call;
|
||||
|
||||
use VLW\API\Databases\VLWdb\VLWdb;
|
||||
use VLW\API\Databases\VLWdb\Models\Work\WorkActionsModel;
|
||||
use VLW\API\Endpoints;
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Work\{
|
||||
WorkModel,
|
||||
WorkActionsModel
|
||||
};
|
||||
|
||||
require_once Path::root("src/Endpoints.php");
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/WorkActions.php");
|
||||
require_once Path::root("src/databases/models/Work/Work.php");
|
||||
require_once Path::root("src/databases/models/Work/WorkActions.php");
|
||||
|
||||
class POST_WorkActions extends VLWdb {
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->ruleset = new Ruleset(strict: true);
|
||||
|
||||
$this->ruleset->GET([
|
||||
(new Rules("id"))
|
||||
->required()
|
||||
->type(Type::STRING)
|
||||
->min(1)
|
||||
->max(parent::MYSQL_VARCHAR_MAX_LENGTH)
|
||||
]);
|
||||
|
||||
$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)
|
||||
|
@ -47,56 +50,31 @@
|
|||
(new Rules(WorkActionsModel::CLASS_LIST->value))
|
||||
->type(Type::ARRAY)
|
||||
->min(1)
|
||||
->max(4)
|
||||
->default([]),
|
||||
|
||||
(new Rules(WorkActionsModel::EXTERNAL->value))
|
||||
->type(Type::BOOLEAN)
|
||||
->default(false)
|
||||
]);
|
||||
|
||||
parent::__construct(Databases::VLW, $this->ruleset);
|
||||
}
|
||||
|
||||
// # Responses
|
||||
|
||||
// Return 422 Unprocessable Content error if request validation failed
|
||||
private function resp_rules_invalid(): Response {
|
||||
return new Response($this->ruleset->get_errors(), 422);
|
||||
}
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to get work data, please try again later", 503);
|
||||
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 request validation failed
|
||||
if (!$this->ruleset->is_valid()) {
|
||||
return $this->resp_rules_invalid();
|
||||
// Bail out if work entity could not be fetched
|
||||
$entity = self::get_entity();
|
||||
if (!$entity->ok) {
|
||||
return $entity;
|
||||
}
|
||||
|
||||
// Ensure an entity with the provided id exists
|
||||
$entity = Call("work?id={$_GET["id"]}", Method::GET);
|
||||
if ($entity->code !== 200) {
|
||||
// Response from endpoint is not 404, something went wrong
|
||||
if ($entity->code !== 404) {
|
||||
return $this->resp_database_error();
|
||||
}
|
||||
|
||||
return new Response("No entity with id '{$_GET["id"]}' was found", 404);
|
||||
}
|
||||
|
||||
// Attempt to create action for entity
|
||||
$insert = $this->db->for(WorkActionsModel::TABLE)
|
||||
->insert([
|
||||
WorkActionsModel::ID->value => parent::gen_uuid4(),
|
||||
WorkActionsModel::ANCHOR->value => $_GET["id"],
|
||||
WorkActionsModel::DISPLAY_TEXT->value => $_POST[WorkActionsModel::DISPLAY_TEXT->value],
|
||||
WorkActionsModel::HREF->value => $_POST[WorkActionsModel::HREF->value],
|
||||
WorkActionsModel::CLASS_LIST->value => implode(",", $_POST[WorkActionsModel::CLASS_LIST->value]),
|
||||
WorkActionsModel::EXTERNAL->value => $_POST[WorkActionsModel::EXTERNAL->value],
|
||||
]);
|
||||
|
||||
// Return 201 Created and entity id as body if insert was successful
|
||||
return $insert === true ? new Response($_GET["id"], 201) : $this->resp_database_error();
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -1,60 +1,52 @@
|
|||
<?php
|
||||
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use VLW\API\Databases\VLWdb\VLWdb;
|
||||
use VLW\API\Databases\VLWdb\Models\WorkPermalinks\WorkPermalinksModel;
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Work\WorkPermalinksModel;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/WorkPermalinks.php");
|
||||
require_once Path::root("src/databases/models/Work/WorkPermalinks.php");
|
||||
|
||||
class GET_WorkPermalinks extends VLWdb {
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->ruleset = new Ruleset(strict: true);
|
||||
|
||||
$this->ruleset->GET([
|
||||
(new Rules("id"))
|
||||
->required()
|
||||
(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)
|
||||
]);
|
||||
}
|
||||
|
||||
// # Responses
|
||||
|
||||
// Return 422 Unprocessable Content error if request validation failed
|
||||
private function resp_rules_invalid(): Response {
|
||||
return new Response($this->ruleset->get_errors(), 422);
|
||||
}
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to resolve permalink, please try again later", 503);
|
||||
parent::__construct(Databases::VLW, $this->ruleset);
|
||||
}
|
||||
|
||||
public function main(): Response {
|
||||
// Bail out if request validation failed
|
||||
if (!$this->ruleset->is_valid()) {
|
||||
return $this->resp_rules_invalid();
|
||||
}
|
||||
$response = $this->db->for(WorkPermalinksModel::TABLE)
|
||||
->where($_GET)
|
||||
->select([
|
||||
WorkPermalinksModel::ID->value,
|
||||
WorkPermalinksModel::REF_WORK_ID->value,
|
||||
WorkPermalinksModel::DATE_CREATED->value
|
||||
]);
|
||||
|
||||
// Get all anchors that match the requested slug
|
||||
$resolve = $this->db->for(WorkPermalinksModel::TABLE)
|
||||
->where([WorkPermalinksModel::SLUG->value => $_GET["id"]])
|
||||
->select(WorkPermalinksModel::ANCHOR->value);
|
||||
|
||||
// Return array of all matched work table ids. Or empty array if none found
|
||||
return parent::is_mysqli_result($resolve)
|
||||
? new Response(array_column($resolve->fetch_all(MYSQLI_ASSOC), WorkPermalinksModel::ANCHOR->value))
|
||||
: $this->resp_database_error();
|
||||
return $response->num_rows > 0
|
||||
? new Response($response->fetch_all(MYSQLI_ASSOC))
|
||||
: new Response([], 404);
|
||||
}
|
||||
}
|
|
@ -1,83 +1,65 @@
|
|||
<?php
|
||||
|
||||
|
||||
use Reflect\Call;
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use Reflect\Method;
|
||||
use function Reflect\Call;
|
||||
|
||||
use VLW\API\Databases\VLWdb\VLWdb;
|
||||
use VLW\API\Databases\VLWdb\Models\WorkPermalinks\WorkPermalinksModel;
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Work\WorkPermalinksModel;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/WorkPermalinks.php");
|
||||
require_once Path::root("src/databases/models/Work/WorkPermalinks.php");
|
||||
|
||||
class POST_WorkPermalinks extends VLWdb {
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->ruleset = new Ruleset(strict: true);
|
||||
|
||||
$this->ruleset->POST([
|
||||
(new Rules("slug"))
|
||||
(new Rules(WorkPermalinksModel::ID->value))
|
||||
->required()
|
||||
->type(Type::STRING)
|
||||
->min(1)
|
||||
->max(parent::MYSQL_VARCHAR_MAX_LENGTH),
|
||||
|
||||
(new Rules("anchor"))
|
||||
(new Rules(WorkPermalinksModel::REF_WORK_ID->value))
|
||||
->required()
|
||||
->type(Type::STRING)
|
||||
->min(1)
|
||||
->max(parent::MYSQL_VARCHAR_MAX_LENGTH)
|
||||
->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);
|
||||
}
|
||||
|
||||
// # Responses
|
||||
|
||||
// Return 422 Unprocessable Content error if request validation failed
|
||||
private function resp_rules_invalid(): Response {
|
||||
return new Response($this->ruleset->get_errors(), 422);
|
||||
}
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to resolve permalink, please try again later", 503);
|
||||
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 request validation failed
|
||||
if (!$this->ruleset->is_valid()) {
|
||||
return $this->resp_rules_invalid();
|
||||
// Bail out if work entity could not be fetched
|
||||
$entity = self::get_entity();
|
||||
if (!$entity->ok) {
|
||||
return $entity;
|
||||
}
|
||||
|
||||
// Check if an entity exists with slug
|
||||
$existing_entity = Call("work?id={$_POST["slug"]}", Method::GET);
|
||||
// Response is not 404 (Not found) so we can't create the entity
|
||||
if ($existing_entity->code !== 200) {
|
||||
// Response is not a valid entity, something went wrong
|
||||
if ($existing_entity->code !== 404) {
|
||||
return $this->resp_database_error();
|
||||
}
|
||||
|
||||
// Return 402 Conflict
|
||||
return new Response("No work entity with id '{$_POST["slug"]}' was found to permalink", 404);
|
||||
}
|
||||
|
||||
// Attempt to create new entity
|
||||
$insert = $this->db->for(WorkPermalinksModel::TABLE)
|
||||
->insert([
|
||||
WorkPermalinksModel::SLUG->value => $_POST["slug"],
|
||||
WorkPermalinksModel::ANCHOR->value => $_POST["anchor"],
|
||||
WorkPermalinksModel::DATE_TIMESTAMP_CREATED->value => time(),
|
||||
]);
|
||||
|
||||
// Return 201 Created and entity slug as body if insert was successful
|
||||
return $insert === true ? new Response($_POST["slug"], 201) : $this->resp_database_error();
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -6,75 +6,37 @@
|
|||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use Reflect\Method;
|
||||
use function Reflect\Call;
|
||||
|
||||
use VLW\API\Databases\VLWdb\VLWdb;
|
||||
use const VLW\API\RESP_DELETE_OK;
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Work\WorkTagsModel;
|
||||
use VLW\API\Databases\VLWdb\Models\Work\WorkTagsNameEnum;
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/WorkTags.php");
|
||||
require_once Path::root("src/databases/models/Work/WorkTags.php");
|
||||
|
||||
class DELETE_WorkTags extends VLWdb {
|
||||
protected Ruleset $ruleset;
|
||||
private Ruleset $ruleset;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->ruleset = new Ruleset(strict: true);
|
||||
|
||||
$this->ruleset->POST([
|
||||
(new Rules("id"))
|
||||
->required()
|
||||
->type(Type::STRING)
|
||||
$this->ruleset->GET([
|
||||
(new Rules(WorkTagsModel::REF_WORK_ID->value))
|
||||
->min(1)
|
||||
->max(parent::MYSQL_VARCHAR_MAX_LENGTH),
|
||||
|
||||
(new Rules(WorkTagsModel::NAME->value))
|
||||
->required()
|
||||
->type(Type::ENUM, WorkTagsNameEnum::names())
|
||||
]);
|
||||
}
|
||||
|
||||
// # Responses
|
||||
|
||||
// Return 422 Unprocessable Content error if request validation failed
|
||||
private function resp_rules_invalid(): Response {
|
||||
return new Response($this->ruleset->get_errors(), 422);
|
||||
}
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to get work data, please try again later", 503);
|
||||
parent::__construct(Databases::VLW, $this->ruleset);
|
||||
}
|
||||
|
||||
public function main(): Response {
|
||||
// Bail out if request validation failed
|
||||
if (!$this->ruleset->is_valid()) {
|
||||
return $this->resp_rules_invalid();
|
||||
}
|
||||
|
||||
// Ensure the tag exists for entity id
|
||||
$existing_tag = $this->db->for(WorkTagsModel::TABLE)
|
||||
->where([
|
||||
WorkTagsModel::ANCHOR->value => $_POST["id"],
|
||||
WorkTagsModel::NAME->value => $_POST["name"]
|
||||
])
|
||||
->select(null);
|
||||
|
||||
// Return idempotent deletion if the tag does not exist
|
||||
if ($existing_tag->num_rows === 0) {
|
||||
return new Response($_POST["id"]);
|
||||
}
|
||||
|
||||
// Attempt to delete tag for entity
|
||||
$delete = $this->db->for(WorkTagsModel::TABLE)
|
||||
->delete([
|
||||
WorkTagsModel::ANCHOR->value => $_POST["id"],
|
||||
WorkTagsModel::NAME->value => $_POST["name"]
|
||||
]);
|
||||
|
||||
// Return 201 Created and entity id as body if insert was successful
|
||||
return $delete === true ? new Response($_POST["id"], 201) : $this->resp_database_error();
|
||||
return $this->db->for(WorkTagsModel::TABLE)->delete($_POST) === true
|
||||
? new Response(RESP_DELETE_OK)
|
||||
: new Response("Failed to delete value from document", 500);
|
||||
}
|
||||
}
|
51
api/endpoints/work/tags/GET.php
Normal file
51
api/endpoints/work/tags/GET.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Work\{
|
||||
WorkTagsModel,
|
||||
WorkTagsNameEnum
|
||||
};
|
||||
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/Work/WorkTags.php");
|
||||
|
||||
class GET_WorkTags extends VLWdb {
|
||||
private Ruleset $ruleset;
|
||||
|
||||
public function __construct() {
|
||||
$this->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($response->fetch_all(MYSQLI_ASSOC))
|
||||
: new Response([], 404);
|
||||
}
|
||||
}
|
|
@ -1,93 +1,63 @@
|
|||
<?php
|
||||
|
||||
use Reflect\Call;
|
||||
use Reflect\Path;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Type;
|
||||
use ReflectRules\Rules;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use Reflect\Method;
|
||||
use function Reflect\Call;
|
||||
|
||||
use VLW\API\Databases\VLWdb\VLWdb;
|
||||
use VLW\API\Databases\VLWdb\Models\Work\WorkTagsModel;
|
||||
use VLW\API\Databases\VLWdb\Models\Work\WorkTagsNameEnum;
|
||||
use VLW\API\Endpoints;
|
||||
use VLW\API\Databases\VLWdb\{
|
||||
VLWdb,
|
||||
Databases
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Work\{
|
||||
WorkModel,
|
||||
WorkTagsModel,
|
||||
WorkTagsNameEnum
|
||||
};
|
||||
|
||||
require_once Path::root("src/Endpoints.php");
|
||||
require_once Path::root("src/databases/VLWdb.php");
|
||||
require_once Path::root("src/databases/models/WorkTags.php");
|
||||
require_once Path::root("src/databases/models/Work/Work.php");
|
||||
require_once Path::root("src/databases/models/Work/WorkTags.php");
|
||||
|
||||
class POST_WorkTags extends VLWdb {
|
||||
protected Ruleset $ruleset;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->ruleset = new Ruleset(strict: true);
|
||||
|
||||
$this->ruleset->GET([
|
||||
(new Rules("id"))
|
||||
->required()
|
||||
->type(Type::STRING)
|
||||
->min(1)
|
||||
->max(parent::MYSQL_VARCHAR_MAX_LENGTH)
|
||||
]);
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
// # Responses
|
||||
|
||||
// Return 422 Unprocessable Content error if request validation failed
|
||||
private function resp_rules_invalid(): Response {
|
||||
return new Response($this->ruleset->get_errors(), 422);
|
||||
}
|
||||
|
||||
// Return a 503 Service Unavailable error if something went wrong with the database call
|
||||
private function resp_database_error(): Response {
|
||||
return new Response("Failed to get work data, please try again later", 503);
|
||||
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 request validation failed
|
||||
if (!$this->ruleset->is_valid()) {
|
||||
return $this->resp_rules_invalid();
|
||||
// Bail out if work entity could not be fetched
|
||||
$entity = self::get_entity();
|
||||
if (!$entity->ok) {
|
||||
return $entity;
|
||||
}
|
||||
|
||||
// Ensure an entity with the provided id exists
|
||||
$entity = Call("work?id={$_GET["id"]}", Method::GET);
|
||||
if ($entity->code !== 200) {
|
||||
// Response from endpoint is not 404, something went wrong
|
||||
if ($entity->code !== 404) {
|
||||
return $this->resp_database_error();
|
||||
}
|
||||
|
||||
return new Response("No entity with id '{$_GET["id"]}' was found", 404);
|
||||
}
|
||||
|
||||
// Ensure the tag does not already exist for entity
|
||||
$existing_tag = $this->db->for(WorkTagsModel::TABLE)
|
||||
->where([
|
||||
WorkTagsModel::ANCHOR->value => $_GET["id"],
|
||||
WorkTagsModel::NAME->value => $_POST["name"]
|
||||
])
|
||||
->select(null);
|
||||
|
||||
// Bail out if this tag already exists
|
||||
if ($existing_tag->num_rows !== 0) {
|
||||
return new Response("Tag '{$_POST["name"]}' is already set on entity id '{$_GET["id"]}'", 402);
|
||||
}
|
||||
|
||||
// Attempt to create tag for entity
|
||||
$insert = $this->db->for(WorkTagsModel::TABLE)
|
||||
->insert([
|
||||
WorkTagsModel::ANCHOR->value => $_GET["id"],
|
||||
WorkTagsModel::NAME->value => $_POST["name"]
|
||||
]);
|
||||
|
||||
// Return 201 Created and entity id as body if insert was successful
|
||||
return $insert === true ? new Response($_GET["id"], 201) : $this->resp_database_error();
|
||||
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);
|
||||
}
|
||||
}
|
2
api/install.sh
Normal file
2
api/install.sh
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Install dependencies
|
||||
composer install --optimize-autoloader
|
29
api/src/Endpoints.php
Normal file
29
api/src/Endpoints.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API;
|
||||
|
||||
// Default string to return when a DELETE request is successful
|
||||
const RESP_DELETE_OK = "OK";
|
||||
|
||||
// Enum of all available VLW endpoints grouped by category
|
||||
enum Endpoints: string {
|
||||
case ABOUT_LANGUAGES = "/about/languages";
|
||||
|
||||
case SEARCH = "/search";
|
||||
|
||||
case MESSAGES = "/messages";
|
||||
|
||||
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";
|
||||
}
|
|
@ -2,24 +2,38 @@
|
|||
|
||||
namespace VLW\API\Databases\VLWdb;
|
||||
|
||||
use libmysqldriver\MySQL;
|
||||
use Reflect\Path;
|
||||
use Reflect\Request;
|
||||
use Reflect\Response;
|
||||
use ReflectRules\Ruleset;
|
||||
|
||||
use vlw\MySQL\MySQL;
|
||||
|
||||
enum Databases: string {
|
||||
case VLW = "vlw";
|
||||
case BATTLESTATION = "battlestation";
|
||||
}
|
||||
|
||||
class VLWdb {
|
||||
const UUID_LENGTH = 36;
|
||||
|
||||
const MYSQL_TEXT_MAX_LENGTH = 65538;
|
||||
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 MySQL $db;
|
||||
protected readonly MySQL $db;
|
||||
|
||||
public function __construct(Databases $database, Ruleset $ruleset) {
|
||||
// Validate provided Ruleset before attempting to connect to the database
|
||||
self::eval_ruleset_or_exit($ruleset);
|
||||
|
||||
public function __construct() {
|
||||
// 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],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -46,7 +60,24 @@
|
|||
);
|
||||
}
|
||||
|
||||
public static function is_mysqli_result(\mysqli_result|bool $resp): bool {
|
||||
return $resp instanceof \mysqli_result;
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
19
api/src/databases/models/Battlestation/Chassis.php
Normal file
19
api/src/databases/models/Battlestation/Chassis.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Battlestation;
|
||||
|
||||
use victorwesterlund\xEnum;
|
||||
|
||||
enum ChassisModel: string {
|
||||
use xEnum;
|
||||
|
||||
const TABLE = "chassis";
|
||||
|
||||
case ID = "id";
|
||||
case VENDOR_NAME = "vendor_name";
|
||||
case VENDOR_MODEL = "vendor_model";
|
||||
case STORAGE_TWOINCHFIVE = "storage_2i5hi";
|
||||
case STORAGE_THREEINCHFIVE = "storage_3i5hi";
|
||||
case DATE_AQUIRED = "date_aquired";
|
||||
case IS_RETIRED = "is_retired";
|
||||
}
|
14
api/src/databases/models/Battlestation/Config/ChassisMb.php
Normal file
14
api/src/databases/models/Battlestation/Config/ChassisMb.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Battlestation\Config;
|
||||
|
||||
use victorwesterlund\xEnum;
|
||||
|
||||
enum ChassisMbModel: string {
|
||||
use xEnum;
|
||||
|
||||
const TABLE = "config_chassis_mb";
|
||||
|
||||
case REF_CHASSIS_ID = "ref_chassis_id";
|
||||
case REF_MB_ID = "ref_mb_id";
|
||||
}
|
15
api/src/databases/models/Battlestation/Config/Config.php
Normal file
15
api/src/databases/models/Battlestation/Config/Config.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Battlestation\Config;
|
||||
|
||||
use victorwesterlund\xEnum;
|
||||
|
||||
enum ConfigModel: string {
|
||||
use xEnum;
|
||||
|
||||
const TABLE = "config";
|
||||
|
||||
case REF_MB_ID = "ref_mb_id";
|
||||
case FRIENDLY_NAME = "friendly_name";
|
||||
case DATE_BUILT = "date_built";
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Battlestation\Config;
|
||||
|
||||
use victorwesterlund\xEnum;
|
||||
|
||||
enum SocketTypeEnum {
|
||||
use xEnum;
|
||||
|
||||
case SLOTTED;
|
||||
case INTEGRATED;
|
||||
}
|
||||
|
||||
enum MbCpuCoolerModel: string {
|
||||
use xEnum;
|
||||
|
||||
const TABLE = "config_mb_cpu_cooler";
|
||||
|
||||
case REF_MB_ID = "ref_mb_id";
|
||||
case REF_CPU_ID = "ref_cpu_id";
|
||||
case REF_COOLER_ID = "ref_cooler_id";
|
||||
case SOCKET = "socket";
|
||||
case SOCKET_TYPE = "socket_type";
|
||||
}
|
23
api/src/databases/models/Battlestation/Config/MbDram.php
Normal file
23
api/src/databases/models/Battlestation/Config/MbDram.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Battlestation\Config;
|
||||
|
||||
use victorwesterlund\xEnum;
|
||||
|
||||
enum SocketTypeModel {
|
||||
use xEnum;
|
||||
|
||||
case SLOTTED;
|
||||
case INTEGRATED;
|
||||
}
|
||||
|
||||
enum MbDramModel: string {
|
||||
use xEnum;
|
||||
|
||||
const TABLE = "config_mb_dram";
|
||||
|
||||
case REF_MB_ID = "ref_mb_id";
|
||||
case REF_DRAM_ID = "ref_dram_id";
|
||||
case SOCKET = "socket";
|
||||
case SOCKET_TYPE = "socket_type";
|
||||
}
|
14
api/src/databases/models/Battlestation/Config/MbGpu.php
Normal file
14
api/src/databases/models/Battlestation/Config/MbGpu.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Battlestation\Config;
|
||||
|
||||
use victorwesterlund\xEnum;
|
||||
|
||||
enum MbGpuModel: string {
|
||||
use xEnum;
|
||||
|
||||
const TABLE = "config_mb_gpu";
|
||||
|
||||
case REF_MB_ID = "ref_mb_id";
|
||||
case REF_GPU_ID = "ref_gpu_id";
|
||||
}
|
14
api/src/databases/models/Battlestation/Config/MbPsu.php
Normal file
14
api/src/databases/models/Battlestation/Config/MbPsu.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Battlestation\Config;
|
||||
|
||||
use victorwesterlund\xEnum;
|
||||
|
||||
enum MbPsuModel: string {
|
||||
use xEnum;
|
||||
|
||||
const TABLE = "config_mb_psu";
|
||||
|
||||
case REF_MB_ID = "ref_mb_id";
|
||||
case REF_PSU_ID = "ref_psu_id";
|
||||
}
|
25
api/src/databases/models/Battlestation/Config/MbStorage.php
Normal file
25
api/src/databases/models/Battlestation/Config/MbStorage.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Battlestation\Config;
|
||||
|
||||
use victorwesterlund\xEnum;
|
||||
|
||||
enum MbStorageSlotFormfactorEnum: string {
|
||||
use xEnum;
|
||||
|
||||
case TWODOTFIVE = "2.5";
|
||||
case THREEDOTFIVE = "3.5";
|
||||
case MDOTTWO = "M.2";
|
||||
case EXTERNAL = "EXTERNAL";
|
||||
}
|
||||
|
||||
enum MbStorageModel: string {
|
||||
use xEnum;
|
||||
|
||||
const TABLE = "config_mb_storage";
|
||||
|
||||
case REF_MB_ID = "ref_mb_id";
|
||||
case REF_STORAGE_ID = "ref_storage_id";
|
||||
case INTERFACE = "interface";
|
||||
case SLOT_FORMFACTOR = "slot_formfactor";
|
||||
}
|
16
api/src/databases/models/Battlestation/Coolers.php
Normal file
16
api/src/databases/models/Battlestation/Coolers.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Battlestation;
|
||||
|
||||
enum CoolersModel: string {
|
||||
const TABLE = "coolers";
|
||||
|
||||
case ID = "id";
|
||||
case TYPE_LIQUID = "type_liquid";
|
||||
case SIZE_FAN = "size_fan";
|
||||
case SIZE_RADIATOR = "size_radiator";
|
||||
case VENDOR_NAME = "vendor_name";
|
||||
case VENDOR_MODEL = "vendor_model";
|
||||
case DATE_AQUIRED = "date_aquired";
|
||||
case IS_RETIRED = "is_retired";
|
||||
}
|
31
api/src/databases/models/Battlestation/Cpu.php
Normal file
31
api/src/databases/models/Battlestation/Cpu.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Battlestation;
|
||||
|
||||
use victorwesterlund\xEnum;
|
||||
|
||||
enum ClassEnum {
|
||||
use xEnum;
|
||||
|
||||
case DESKTOP;
|
||||
case LAPTOP;
|
||||
case SERVER;
|
||||
}
|
||||
|
||||
enum CpuModel: string {
|
||||
use xEnum;
|
||||
|
||||
const TABLE = "cpu";
|
||||
|
||||
case ID = "id";
|
||||
case CPU_CLASS = "class";
|
||||
case CLOCK_BASE = "clock_base";
|
||||
case CLOCK_TURBO = "clock_turbo";
|
||||
case CORE_COUNT_PERFORMANCE = "core_count_performance";
|
||||
case CORE_COUNT_EFFICIENCY = "core_count_efficiency";
|
||||
case CORE_THREADS = "core_threads";
|
||||
case VENDOR_NAME = "vendor_name";
|
||||
case VENDOR_MODEL = "vendor_model";
|
||||
case DATE_AQUIRED = "date_aquired";
|
||||
case IS_RETIRED = "is_retired";
|
||||
}
|
37
api/src/databases/models/Battlestation/Dram.php
Normal file
37
api/src/databases/models/Battlestation/Dram.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Battlestation;
|
||||
|
||||
use victorwesterlund\xEnum;
|
||||
|
||||
enum DramFormfactorEnum {
|
||||
use xEnum;
|
||||
|
||||
case DIMM;
|
||||
case SODIMM;
|
||||
}
|
||||
|
||||
enum DramTechnologyEnum {
|
||||
use xEnum;
|
||||
|
||||
case DDR4;
|
||||
case DDR5;
|
||||
}
|
||||
|
||||
enum DramModel: string {
|
||||
use xEnum;
|
||||
|
||||
const TABLE = "dram";
|
||||
|
||||
case ID = "id";
|
||||
case CAPACITY = "capacity";
|
||||
case SPEED = "speed";
|
||||
case FORMFACTOR = "formfactor";
|
||||
case TECHNOLOGY = "technology";
|
||||
case ECC = "ecc";
|
||||
case BUFFERED = "buffered";
|
||||
case VENDOR_NAME = "vendor_name";
|
||||
case VENDOR_MODEL = "vendor_model";
|
||||
case DATE_AQUIRED = "date_aquired";
|
||||
case IS_RETIRED = "is_retired";
|
||||
}
|
20
api/src/databases/models/Battlestation/Gpu.php
Normal file
20
api/src/databases/models/Battlestation/Gpu.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Battlestation;
|
||||
|
||||
use victorwesterlund\xEnum;
|
||||
|
||||
enum GpuModel: string {
|
||||
use xEnum;
|
||||
|
||||
const TABLE = "gpu";
|
||||
|
||||
case ID = "id";
|
||||
case MEMORY = "memory";
|
||||
case VENDOR_NAME = "vendor_name";
|
||||
case VENDOR_MODEL = "vendor_model";
|
||||
case VENDOR_CHIP_NAME = "vendor_chip_name";
|
||||
case VENDOR_CHIP_MODEL = "vendor_chip_model";
|
||||
case DATE_AQUIRED = "date_aquired";
|
||||
case IS_RETIRED = "is_retired";
|
||||
}
|
30
api/src/databases/models/Battlestation/Mb.php
Normal file
30
api/src/databases/models/Battlestation/Mb.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Battlestation;
|
||||
|
||||
use victorwesterlund\xEnum;
|
||||
|
||||
enum MbFormfactorEnum {
|
||||
use xEnum;
|
||||
|
||||
case ATX;
|
||||
case MTX;
|
||||
case ITX;
|
||||
case LAPTOP;
|
||||
}
|
||||
|
||||
enum MbModel: string {
|
||||
use xEnum;
|
||||
|
||||
const TABLE = "mb";
|
||||
|
||||
case ID = "id";
|
||||
case FORMFACTOR = "formfactor";
|
||||
case VENDOR_NAME = "vendor_name";
|
||||
case VENDOR_MODEL = "vendor_model";
|
||||
case NETWORK_ETHERNET = "network_ethernet";
|
||||
case NETWORK_WLAN = "network_wlan";
|
||||
case NETWORK_BLUETOOTH = "network_bluetooth";
|
||||
case DATE_AQUIRED = "date_aquired";
|
||||
case IS_RETIRED = "is_retired";
|
||||
}
|
31
api/src/databases/models/Battlestation/Psu.php
Normal file
31
api/src/databases/models/Battlestation/Psu.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Battlestation;
|
||||
|
||||
use victorwesterlund\xEnum;
|
||||
|
||||
enum EightyplusRatingEnum {
|
||||
use xEnum;
|
||||
|
||||
case BASE;
|
||||
case BRONZE;
|
||||
case SILVER;
|
||||
case GOLD;
|
||||
case PLATINUM;
|
||||
case TITANIUM;
|
||||
}
|
||||
|
||||
enum PsuModel: string {
|
||||
use xEnum;
|
||||
|
||||
const TABLE = "psu";
|
||||
|
||||
case ID = "id";
|
||||
case POWER = "power";
|
||||
case VENDOR_NAME = "vendor_name";
|
||||
case VENDOR_MODEL = "vendor_model";
|
||||
case TYPE_MODULAR = "type_modular";
|
||||
case EIGHTYPLUS_RATING = "80plus_rating";
|
||||
case DATE_AQUIRED = "date_aquired";
|
||||
case IS_RETIRED = "is_retired";
|
||||
}
|
45
api/src/databases/models/Battlestation/Storage.php
Normal file
45
api/src/databases/models/Battlestation/Storage.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Battlestation;
|
||||
|
||||
use victorwesterlund\xEnum;
|
||||
|
||||
enum StorageDiskTypeEnum {
|
||||
use xEnum;
|
||||
|
||||
case SSD;
|
||||
case HDD;
|
||||
}
|
||||
|
||||
enum StorageDiskInterfaceEnum {
|
||||
use xEnum;
|
||||
|
||||
case SATA;
|
||||
case NVME;
|
||||
case USB;
|
||||
}
|
||||
|
||||
enum StorageDiskFormfactorEnum{
|
||||
use xEnum;
|
||||
|
||||
case TWODOTFIVE;
|
||||
case THREEDOTFIVE;
|
||||
case MDOTTWO;
|
||||
}
|
||||
|
||||
enum StorageModel: string {
|
||||
use xEnum;
|
||||
|
||||
const TABLE = "storage";
|
||||
|
||||
case ID = "id";
|
||||
case DISK_TYPE = "disk_type";
|
||||
case DISK_SIZE = "disk_size";
|
||||
case DISK_SECTORS = "disk_sectors";
|
||||
case DISK_INTERFACE = "disk_interface";
|
||||
case DISK_FORMFACTOR = "disk_formfactor";
|
||||
case VENDOR_NAME = "vendor_name";
|
||||
case VENDOR_MODEL = "vendor_model";
|
||||
case DATE_AQUIRED = "date_aquired";
|
||||
case IS_RETIRED = "is_retired";
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Coffee;
|
||||
|
||||
enum CoffeeModel: string {
|
||||
const TABLE = "coffee";
|
||||
|
||||
case ID = "id";
|
||||
case DATE_TIMESTAMP_CREATED = "date_timestamp_created";
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Media;
|
||||
|
||||
use victorwesterlund\xEnum;
|
||||
|
||||
enum MediaTypeEnum: string {
|
||||
use xEnum;
|
||||
|
||||
case BLOB = "BLOB";
|
||||
case IMAGE = "IMAGE";
|
||||
}
|
||||
|
||||
enum MediaModel: string {
|
||||
const TABLE = "media";
|
||||
|
||||
case ID = "id";
|
||||
case NAME = "name";
|
||||
case TYPE = "type";
|
||||
case MIME = "mime";
|
||||
case EXTENSION = "extension";
|
||||
case SRCSET = "srcset";
|
||||
case DATE_TIMESTAMP_CREATED = "date_timestamp_created";
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\MediaSrcset;
|
||||
|
||||
enum MediaSrcsetModel: string {
|
||||
const TABLE = "media_srcset";
|
||||
|
||||
case ID = "id";
|
||||
case ANCHOR_DEFAULT = "anchor_default";
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Messages;
|
||||
|
||||
enum MessagesModel: string {
|
||||
const TABLE = "messages";
|
||||
|
||||
case ID = "id";
|
||||
case EMAIL = "email";
|
||||
case MESSAGE = "message";
|
||||
case IS_READ = "is_read";
|
||||
case IS_SPAM = "is_spam";
|
||||
case IS_SAVED = "is_saved";
|
||||
case DATE_TIMESTAMP_CREATED = "date_timestamp_created";
|
||||
}
|
15
api/src/databases/models/Messages/Messages.php
Normal file
15
api/src/databases/models/Messages/Messages.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Messages;
|
||||
|
||||
enum MessagesModel: string {
|
||||
const TABLE = "messages";
|
||||
|
||||
case ID = "id";
|
||||
case EMAIL = "email";
|
||||
case MESSAGE = "message";
|
||||
case IS_READ = "is_read";
|
||||
case IS_SPAM = "is_spam";
|
||||
case IS_SAVED = "is_saved";
|
||||
case DATE_CREATED = "date_created";
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Work;
|
||||
|
||||
enum WorkModel: string {
|
||||
const TABLE = "work";
|
||||
|
||||
case ID = "id";
|
||||
case TITLE = "title";
|
||||
case SUMMARY = "summary";
|
||||
case COVER_SRCSET = "cover_srcset";
|
||||
case IS_LISTABLE = "is_listable";
|
||||
case IS_READABLE = "is_readable";
|
||||
case DATE_YEAR = "date_year";
|
||||
case DATE_MONTH = "date_month";
|
||||
case DATE_DAY = "date_day";
|
||||
case DATE_TIMESTAMP_MODIFIED = "date_timestamp_modified";
|
||||
case DATE_TIMESTAMP_CREATED = "date_timestamp_created";
|
||||
}
|
19
api/src/databases/models/Work/Work.php
Normal file
19
api/src/databases/models/Work/Work.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Work;
|
||||
|
||||
enum WorkModel: string {
|
||||
const TABLE = "work";
|
||||
|
||||
case ID = "id";
|
||||
case TITLE = "title";
|
||||
case SUMMARY = "summary";
|
||||
case COVER_SRCSET = "cover_srcset";
|
||||
case IS_LISTABLE = "is_listable";
|
||||
case IS_READABLE = "is_readable";
|
||||
case DATE_YEAR = "date_year";
|
||||
case DATE_MONTH = "date_month";
|
||||
case DATE_DAY = "date_day";
|
||||
case DATE_MODIFIED = "date_modified";
|
||||
case DATE_CREATED = "date_created";
|
||||
}
|
3
api/src/databases/models/WorkActions.php → api/src/databases/models/Work/WorkActions.php
Executable file → Normal file
3
api/src/databases/models/WorkActions.php → api/src/databases/models/Work/WorkActions.php
Executable file → Normal file
|
@ -5,8 +5,7 @@
|
|||
enum WorkActionsModel: string {
|
||||
const TABLE = "work_actions";
|
||||
|
||||
case ID = "id";
|
||||
case ANCHOR = "anchor";
|
||||
case REF_WORK_ID = "ref_work_id";
|
||||
case DISPLAY_TEXT = "display_text";
|
||||
case HREF = "href";
|
||||
case CLASS_LIST = "class_list";
|
0
api/src/databases/models/WorkMedia.php → api/src/databases/models/Work/WorkMedia.php
Executable file → Normal file
0
api/src/databases/models/WorkMedia.php → api/src/databases/models/Work/WorkMedia.php
Executable file → Normal file
2
api/src/databases/models/WorkPermalinks.php → api/src/databases/models/Work/WorkPermalinks.php
Executable file → Normal file
2
api/src/databases/models/WorkPermalinks.php → api/src/databases/models/Work/WorkPermalinks.php
Executable file → Normal file
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\WorkPermalinks;
|
||||
namespace VLW\API\Databases\VLWdb\Models\Work;
|
||||
|
||||
enum WorkPermalinksModel: string {
|
||||
const TABLE = "work_permalinks";
|
20
api/src/databases/models/Work/WorkTags.php
Normal file
20
api/src/databases/models/Work/WorkTags.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Work;
|
||||
|
||||
use victorwesterlund\xEnum;
|
||||
|
||||
enum WorkTagsNameEnum {
|
||||
use xEnum;
|
||||
|
||||
case VLW;
|
||||
case RELEASE;
|
||||
case WEBSITE;
|
||||
}
|
||||
|
||||
enum WorkTagsModel: string {
|
||||
const TABLE = "work_tags";
|
||||
|
||||
case REF_WORK_ID = "ref_work_id";
|
||||
case NAME = "name";
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API\Databases\VLWdb\Models\Work;
|
||||
|
||||
use victorwesterlund\xEnum;
|
||||
|
||||
enum WorkTagsNameEnum: string {
|
||||
use xEnum;
|
||||
|
||||
case VLW = "VLW";
|
||||
case RELEASE = "RELEASE";
|
||||
case WEBSITE = "WEBSITE";
|
||||
}
|
||||
|
||||
enum WorkTagsModel: string {
|
||||
const TABLE = "work_tags";
|
||||
|
||||
case ANCHOR = "anchor";
|
||||
case NAME = "name";
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"name": "local/api.endpoints",
|
||||
"description": "Endpoint pathmappings for VLW API",
|
||||
"type": "library",
|
||||
"version": "1.0.0-dev",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Victor Westerlund",
|
||||
"email": "victor@vlw.se"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"VLW\\API\\": "src/"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace VLW\API;
|
||||
|
||||
enum Endpoints: string {
|
||||
case WORK = "/work";
|
||||
case SEARCH = "/search";
|
||||
case MESSAGES = "/messages";
|
||||
case WORK_TAGS = "/work/tags";
|
||||
case WORK_ACTIONS = "/work/actions";
|
||||
}
|
|
@ -1,325 +0,0 @@
|
|||
:root {
|
||||
--primer-color-accent: 255, 255, 0;
|
||||
--color-accent: yellow;
|
||||
--hue-accent: 0deg;
|
||||
|
||||
--padding: 20px;
|
||||
--running-size: 80px;
|
||||
}
|
||||
|
||||
/* # Cornerstones */
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: "Roboto Mono", sans-serif;
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body {
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
grid-template-rows: var(--running-size) 1fr;
|
||||
overscroll-behavior: none;
|
||||
background-color: black;
|
||||
color: white;
|
||||
overflow-x: hidden;
|
||||
min-height: 100svh;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
body.search-dialog-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
a {
|
||||
display: contents;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* # Components */
|
||||
|
||||
:is(h1, h2, h3, p, li) > a {
|
||||
--underline-tickness: 3px;
|
||||
|
||||
display: initial;
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: var(--underline-tickness);
|
||||
text-underline-offset: var(--underline-tickness);
|
||||
text-decoration-color: var(--color-accent);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 30px;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
/* ## Buttons */
|
||||
|
||||
button {
|
||||
padding: calc(var(--padding) / 2) var(--padding);
|
||||
color: white;
|
||||
border: solid 2px white;
|
||||
border-radius: 6px;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.solid {
|
||||
color: black;
|
||||
border-color: var(--color-accent);
|
||||
background-color: var(--color-accent);
|
||||
}
|
||||
|
||||
a > button::after {
|
||||
content: " ➜";
|
||||
}
|
||||
|
||||
a[target="_blank"] > button::after,
|
||||
:is(h1, h2, h3, p, li) > a[target="_blank"]::after {
|
||||
content: " ⮥";
|
||||
color: var(--color-accent);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
a > button.solid:not(:hover)::after {
|
||||
color: black;
|
||||
}
|
||||
|
||||
/* ## Header */
|
||||
|
||||
header {
|
||||
--border-style: solid 1px rgba(255, 255, 255, .2);
|
||||
|
||||
position: sticky;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: var(--running-size);
|
||||
border-bottom: var(--border-style);
|
||||
display: grid;
|
||||
align-items: stretch;
|
||||
justify-items: end;
|
||||
grid-template-columns: 1fr var(--running-size);
|
||||
background-color: rgba(0, 0, 0, .8);
|
||||
z-index: 100;
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
|
||||
header nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--padding);
|
||||
}
|
||||
|
||||
header .logo {
|
||||
width: calc(var(--running-size) - 1px);
|
||||
height: calc(var(--running-size) - 1px);
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
border-left: var(--border-style);
|
||||
}
|
||||
|
||||
header .logo path.stroke {
|
||||
fill: var(--color-accent);
|
||||
}
|
||||
|
||||
header searchbox {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ## Main */
|
||||
|
||||
main {
|
||||
transition: 400ms transform;
|
||||
position: relative;
|
||||
padding: calc(var(--padding) * 1.5);
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
main > * {
|
||||
transition: 100ms opacity;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
main.loading > * {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* ## Search */
|
||||
|
||||
/* ### Box */
|
||||
|
||||
searchbox {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
border-left: var(--border-style);
|
||||
grid-template-columns: 25px 1fr;
|
||||
align-items: center;
|
||||
padding: var(--padding);
|
||||
gap: var(--padding);
|
||||
fill: var(--color-accent);
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, .5);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ### Dialog */
|
||||
|
||||
body.search-dialog-open main {
|
||||
transform: scale(.94);
|
||||
}
|
||||
|
||||
dialog.search {
|
||||
transition: 200ms height cubic-bezier(.41,0,.34,.99);
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
height: calc(var(--running-size) + (var(--padding) * 5));
|
||||
max-height: 1000px;
|
||||
border-color: transparent;
|
||||
background-color: transparent;
|
||||
overflow: visible;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
dialog.search.active {
|
||||
height: 70vh;
|
||||
}
|
||||
|
||||
dialog.search search {
|
||||
transition: 400ms transform, 200ms opacity;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-rows: var(--running-size) 1fr;
|
||||
gap: calc(var(--padding) * 2);
|
||||
transform: scale(1.1);
|
||||
overflow: hidden;
|
||||
background-color: rgba(255, 255, 255, .05);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
backdrop-filter: brightness(.3) blur(20px);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 30px 10px black;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
body.search-dialog-open dialog.search search {
|
||||
transform: scale(1);
|
||||
padding: calc(var(--padding) * 1.5);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
search input {
|
||||
transition: 200ms background-color, 200ms box-shadow, 200ms color;
|
||||
border-radius: 6px;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: black;
|
||||
font-size: 16px;
|
||||
padding: var(--padding) calc(var(--padding) * 1.5);
|
||||
background-color: rgba(255, 255, 255, .05);
|
||||
box-shadow: 0 5px 70px 10px rgba(0, 0, 0, .3);
|
||||
color: white;
|
||||
}
|
||||
|
||||
search input:focus {
|
||||
background-color: rgba(255, 255, 255, .9);
|
||||
box-shadow: 0 10px 30px 10px black;
|
||||
color: black;
|
||||
}
|
||||
|
||||
/* ### Search results */
|
||||
|
||||
dialog.search search search-results {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
dialog.search search search-results > svg {
|
||||
margin: auto;
|
||||
width: 150px;
|
||||
fill: rgba(255, 255, 255, .05);
|
||||
}
|
||||
|
||||
/* # Feature queries */
|
||||
|
||||
@media (hover: hover) {
|
||||
:is(h1, h2, h3, p, li) > a:hover {
|
||||
text-underline-offset: 1px;
|
||||
text-decoration-thickness: calc(var(--underline-tickness) * 2);
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
/* # Components */
|
||||
|
||||
button {
|
||||
transition: 200ms background-color, 200ms border-color, 200ms color;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border-color: rgba(255, 255, 255, .2);
|
||||
background-color: rgba(255, 255, 255, .1);
|
||||
}
|
||||
|
||||
button.solid:hover {
|
||||
color: var(--color-accent);
|
||||
border-color: rgba(var(--primer-color-accent), .2);
|
||||
background-color: rgba(var(--primer-color-accent), .2);
|
||||
box-shadow: 0 -10px 20px 10px rgba(var(--primer-color-accent), .05);
|
||||
}
|
||||
|
||||
/* ## Header */
|
||||
|
||||
header .logo:hover path.solid {
|
||||
fill: var(--color-accent);
|
||||
}
|
||||
|
||||
searchbox {
|
||||
transition: 200ms background-color;
|
||||
}
|
||||
|
||||
searchbox:hover {
|
||||
background-color: rgba(255, 255, 255, .07);
|
||||
}
|
||||
}
|
||||
|
||||
/* # Size queries */
|
||||
|
||||
@media (min-width: 700px) {
|
||||
header {
|
||||
grid-template-columns: 1fr 250px var(--running-size);
|
||||
}
|
||||
|
||||
header nav {
|
||||
justify-self: start;
|
||||
margin: 0 calc(var(--padding) / 2);
|
||||
}
|
||||
|
||||
/* # Menu */
|
||||
|
||||
/* < Move the search box to the header */
|
||||
header searchbox {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
menu searchbox {
|
||||
display: none;
|
||||
}
|
||||
/* /> */
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
@font-face {
|
||||
font-family: "Roboto Mono";
|
||||
ascent-override: 100%;
|
||||
font-weight: 400;
|
||||
size-adjust: 105%;
|
||||
font-stretch: 97.5% 112.5%;
|
||||
src: local("Roboto Mono Regular"), url("/assets/fonts/roboto-mono-regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Roboto Mono";
|
||||
ascent-override: 100%;
|
||||
size-adjust: 95%;
|
||||
font-weight: 800;
|
||||
src: local("Roboto Mono Bold"), url("/assets/fonts/roboto-mono-bold.woff2") format("woff2");
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -1,72 +0,0 @@
|
|||
new vv.Interactions("document");
|
||||
|
||||
const mainElement = document.querySelector(vv._env.MAIN);
|
||||
|
||||
// Crossfade pages on navigation
|
||||
// Or maybe I shouldn't... hmmm
|
||||
mainElement.addEventListener(vv.Navigation.events.LOADING, () => {
|
||||
mainElement.classList.add("loading");
|
||||
|
||||
// Clean up modified transform-origin if set after search dialog animation
|
||||
mainElement.style.removeProperty("transform-origin");
|
||||
});
|
||||
|
||||
mainElement.addEventListener(vv.Navigation.events.LOADED, () => {
|
||||
[...document.querySelectorAll("dialog")].forEach(element => element.close())
|
||||
|
||||
// Wait 200ms for the page fade-in animation to finish
|
||||
setTimeout(() => mainElement.classList.remove("loading"), 200);
|
||||
});
|
||||
|
||||
// Search dialog open/close logic
|
||||
{
|
||||
const CLASNAME_DIALOG_OPEN = "search-dialog-open";
|
||||
// Offset in pixels from scroll position when scaling the main element
|
||||
const TRANSFORM_ORIGIN_Y_PADDING = 350;
|
||||
|
||||
const dialog = document.querySelector("dialog.search");
|
||||
|
||||
// "Polyfill" for HTMLDialogELement open and close events
|
||||
(new MutationObserver((mutations) => {
|
||||
// There is only one search dialog elemenet
|
||||
const target = mutations[0].target;
|
||||
|
||||
// Set or unset dialog open class on body depending on dialog visibility
|
||||
target.hasAttribute("open")
|
||||
? target.dispatchEvent(new Event("open"))
|
||||
: target.dispatchEvent(new Event("close"));
|
||||
|
||||
}).observe(dialog, { attributes: true }));
|
||||
|
||||
dialog.addEventListener("open", () => {
|
||||
// Scale main element from the current scroll position
|
||||
mainElement.style.setProperty("transform-origin", `50% calc(${window.scrollY}px + ${TRANSFORM_ORIGIN_Y_PADDING}px)`);
|
||||
|
||||
document.body.classList.add(CLASNAME_DIALOG_OPEN);
|
||||
});
|
||||
dialog.addEventListener("close", () => document.body.classList.remove(CLASNAME_DIALOG_OPEN));
|
||||
|
||||
// Close search dialog if dialog is clicked outside inner content
|
||||
dialog.addEventListener("click", (event) => event.target === dialog ? dialog.close() : null);
|
||||
|
||||
// Open search dialog when searchbox is clicked
|
||||
document.querySelector("searchbox").addEventListener("click", () => dialog.showModal());
|
||||
}
|
||||
|
||||
// Search logic
|
||||
{
|
||||
const searchResultsElement = document.querySelector("search-results");
|
||||
const search = (query) => {
|
||||
new vv.Navigation(`/search?q=${query}`, {
|
||||
carrySearchParams: true
|
||||
}).navigate(searchResultsElement);
|
||||
};
|
||||
|
||||
// Run search on keyup
|
||||
document.querySelector("search input").addEventListener("keyup", (event) => search(event.target.value));
|
||||
|
||||
// Trigger expand search box animation
|
||||
document.querySelector("search input").addEventListener("keydown", () => {
|
||||
searchResultsElement.closest("dialog").classList.add("active");
|
||||
}, { once: true });
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
const EMAIL_CPY_ANIM_DUR_MSECONDS = 1000;
|
||||
|
||||
// Run email copied splash animation
|
||||
const emailCopiedAnimation = () => {
|
||||
const CONFETTI_COUNT = 40;
|
||||
const CONFETTI_SCALE_PIXELS = 300;
|
||||
|
||||
const randomIntFromInterval = (min, max) => {
|
||||
return Math.floor(Math.random() * (max - min + 1) + min)
|
||||
}
|
||||
|
||||
// Create new splash element
|
||||
const splashElement = document.createElement("splash");
|
||||
splashElement.innerText = "copied!";
|
||||
|
||||
// Set inline display to none to hide this element on pages where the splash element has no override styles defined.
|
||||
splashElement.style.display = "none";
|
||||
|
||||
// Array of box-shadow strings as "confetti"
|
||||
const confetti = [];
|
||||
|
||||
// Generate random confetti
|
||||
for (let i = 0; i < CONFETTI_COUNT; i++) {
|
||||
// Random confetti position
|
||||
const x = randomIntFromInterval(CONFETTI_SCALE_PIXELS * -1, CONFETTI_SCALE_PIXELS);
|
||||
const y = randomIntFromInterval(CONFETTI_SCALE_PIXELS * -1, CONFETTI_SCALE_PIXELS);
|
||||
|
||||
// Random confetti RGB color
|
||||
const rgb = [
|
||||
randomIntFromInterval(0, 255),
|
||||
randomIntFromInterval(0, 255),
|
||||
randomIntFromInterval(0, 255)
|
||||
];
|
||||
|
||||
// Interpolate random values and append to outer confetti array
|
||||
confetti.push(`${x}px ${y}px 0 rgb(${rgb.join(",")})`);
|
||||
}
|
||||
|
||||
// Set CSS variable on splash element that in turn will be used by pseudo-element
|
||||
splashElement.style.setProperty("--confetti", confetti.join(","));
|
||||
|
||||
// Start animation by appending the created element to the document body
|
||||
document.body.appendChild(splashElement);
|
||||
|
||||
// Run hide animation
|
||||
setTimeout(() => {
|
||||
splashElement.classList.add("hide");
|
||||
|
||||
// Selfdestruct element when hide animation finishes
|
||||
setTimeout(() => splashElement.remove(), 400);
|
||||
}, EMAIL_CPY_ANIM_DUR_MSECONDS + 100);
|
||||
}
|
||||
|
||||
new vv.Interactions("index", {
|
||||
// Copy email address to clipboard
|
||||
copyEmail: async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText("victor@vlw.se");
|
||||
|
||||
// Run "email copied" animation!
|
||||
emailCopiedAnimation();
|
||||
|
||||
// NOTE: I don't know, spamming the button is kinda fun
|
||||
// Prevent interactions with the copy email elements while the animation is running
|
||||
/*[...document.querySelectorAll("[vv-call='copyEmail']")].forEach(element => {
|
||||
//element.classList.add("lock");
|
||||
|
||||
setTimeout(() => element.classList.remove("lock"), EMAIL_CPY_ANIM_DUR_MSECONDS);
|
||||
});*/
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
},
|
||||
// Open the fullscreen menu
|
||||
openMenu: () => document.querySelector("menu").classList.add("active"),
|
||||
// Close the fullscreen menu
|
||||
closeMenu: () => document.querySelector("menu").classList.remove("active")
|
||||
});
|
||||
|
||||
// Change site accent color on hover of menu items
|
||||
if (window.matchMedia("(hover: hover)")) {
|
||||
// Update root CSS variables
|
||||
const updateColor = (rgb = null, hue = 0) => {
|
||||
if (!rgb) {
|
||||
document.documentElement.style.removeProperty("--hue-accent");
|
||||
document.documentElement.style.removeProperty("--primer-color-accent");
|
||||
document.documentElement.style.removeProperty("--color-accent");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
document.documentElement.style.setProperty("--hue-accent", `${hue}deg`);
|
||||
|
||||
document.documentElement.style.setProperty("--primer-color-accent", `${rgb}`);
|
||||
// Compiled color variable must to be updated to receive the new RGB values
|
||||
document.documentElement.style.setProperty("--color-accent", "rgb(var(--primer-color-accent)");
|
||||
};
|
||||
|
||||
[...document.querySelectorAll("menu li")].forEach(element => {
|
||||
// Change site accent color to RGB and HUE rotation defined in element dataset
|
||||
element.addEventListener("mouseenter", (event) => updateColor(event.target.dataset.rgb, event.target.dataset.hue));
|
||||
// Reset initial accent color and hues
|
||||
element.addEventListener("mouseleave", () => updateColor());
|
||||
});
|
||||
|
||||
// Reset color on navigation
|
||||
document.querySelector(vv._env.MAIN).addEventListener(vv.Navigation.events.LOADED, () => updateColor(), { once: true });
|
||||
}
|
||||
|
||||
// Open search box from mobile fullscreen menu
|
||||
{
|
||||
// Open search dialog when searchbox is clicked
|
||||
document.querySelector("menu searchbox").addEventListener("click", () => {
|
||||
// Search box dialog element
|
||||
document.querySelector("dialog.search").showModal();
|
||||
|
||||
// Close fullscreen menu
|
||||
document.querySelector("menu").classList.remove("active");
|
||||
});
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// Don't open the search dialog overlay if search page is open stand-alone
|
||||
{
|
||||
const searchBox = document.querySelector("body:not(.search-dialog-open) searchbox");
|
||||
|
||||
// Page is stand-alone
|
||||
if (searchBox) {
|
||||
// Shift focus to the on-page search box instead of opening search dialog on click
|
||||
const shiftSearchboxFocus = () => {
|
||||
// Override normal "open search dialog" behavior
|
||||
document.querySelector("dialog.search").close();
|
||||
|
||||
// Shift focus to the on-page search input instead
|
||||
}
|
||||
|
||||
// Bind event listener to searchbox element
|
||||
document.querySelector("body:not(.search-dialog-open) searchbox").addEventListener("click", shiftSearchboxFocus, true);
|
||||
|
||||
// Remove event listener from searchbox element on page navigation
|
||||
mainElement.addEventListener(vv.Navigation.events.LOADING, () => {
|
||||
searchBox.removeEventListener("click", shiftSearchboxFocus);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
new vv.Interactions("search");
|
|
@ -1 +0,0 @@
|
|||
new vv.Interactions("work");
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98 98"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/></svg>
|
Before Width: | Height: | Size: 957 B |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 6.8 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="22"><g fill="none"><path class="solid" d="M12 22 0 0h24Z"/><path class="stroke" d="M12 17.823 20.63 2H3.37L12 17.823M12 22 0 0h24L12 22Z"/><g opacity=".5"><path class="solid" d="M24 22 12 0h24Z"/><path class="stroke" d="M24 17.823 32.63 2H15.37L24 17.823M24 22 12 0h24L24 22Z"/></g></g></svg>
|
Before Width: | Height: | Size: 351 B |
|
@ -1,17 +1,7 @@
|
|||
{
|
||||
"require": {
|
||||
"local/api.client": "1.0.0-dev",
|
||||
"local/api.endpoints": "1.0.0-dev"
|
||||
"reflect/client": "dev-master",
|
||||
"victorwesterlund/xenum": "dev-master"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"repositories": [
|
||||
{
|
||||
"type": "path",
|
||||
"url": "src/packages/API"
|
||||
},
|
||||
{
|
||||
"type": "path",
|
||||
"url": "api/src/packages/Endpoints"
|
||||
}
|
||||
]
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
|
|
100
composer.lock
generated
100
composer.lock
generated
|
@ -4,64 +4,11 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "73a61bf0308871f9dc9ad050aedfe13e",
|
||||
"content-hash": "2a8a06dc452a4eb9055d238f771dcd37",
|
||||
"packages": [
|
||||
{
|
||||
"name": "local/api.client",
|
||||
"version": "1.0.0-dev",
|
||||
"dist": {
|
||||
"type": "path",
|
||||
"url": "src/packages/API",
|
||||
"reference": "020275feb0e0017fa91ae0b33213bc54f35cac75"
|
||||
},
|
||||
"require": {
|
||||
"reflect/client": "^3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"VLW\\API\\": "src/"
|
||||
}
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Victor Westerlund",
|
||||
"email": "victor@vlw.se"
|
||||
}
|
||||
],
|
||||
"description": "Wrapper for vlw.se API",
|
||||
"transport-options": {
|
||||
"relative": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "local/api.endpoints",
|
||||
"version": "1.0.0-dev",
|
||||
"dist": {
|
||||
"type": "path",
|
||||
"url": "api/src/packages/Endpoints",
|
||||
"reference": "89b7b9a4cc504abddb4aeec8e05a95c9d9087575"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"VLW\\API\\": "src/"
|
||||
}
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Victor Westerlund",
|
||||
"email": "victor@vlw.se"
|
||||
}
|
||||
],
|
||||
"description": "Endpoint pathmappings for VLW API",
|
||||
"transport-options": {
|
||||
"relative": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "reflect/client",
|
||||
"version": "3.0.6",
|
||||
"version": "dev-master",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/VictorWesterlund/reflect-client-php.git",
|
||||
|
@ -73,6 +20,7 @@
|
|||
"reference": "89a8c041044c8c60cefafc4716d5d61b96c43e06",
|
||||
"shasum": ""
|
||||
},
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@ -95,14 +43,52 @@
|
|||
"source": "https://github.com/VictorWesterlund/reflect-client-php/tree/3.0.6"
|
||||
},
|
||||
"time": "2024-04-06T14:55:04+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"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "dev",
|
||||
"stability-flags": {
|
||||
"local/api.client": 20,
|
||||
"local/api.endpoints": 20
|
||||
"reflect/client": 20,
|
||||
"victorwesterlund/xenum": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
|
|
10
install.sh
Normal file
10
install.sh
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Install dependencies
|
||||
composer install --optimize-autoloader
|
||||
npm install
|
||||
|
||||
# (Re)create public NPM modules folder
|
||||
rm -r public/assets/js/modules/npm
|
||||
mkdir public/assets/js/modules/npm
|
||||
|
||||
# Create link to Elevent MJS from public JS modules folder
|
||||
ln -sr node_modules/elevent/src/Elevent.mjs public/assets/js/modules/npm/Elevent.mjs
|
17
package-lock.json
generated
Normal file
17
package-lock.json
generated
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "vlw.se",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"elevent": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/elevent": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/elevent/-/elevent-1.0.2.tgz",
|
||||
"integrity": "sha512-ks5LBUBTg4Bpfmj99OcFAzuDGzBRDEZhTyxmq/Y3RbsdBQ4JCaIUYB0M15OBvBWgIn1BnCo4WCSmw0/YbCJliw=="
|
||||
}
|
||||
}
|
||||
}
|
5
package.json
Normal file
5
package.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"elevent": "^1.0.2"
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
<style><?= VV::css("pages/about") ?></style>
|
||||
<section class="intro">
|
||||
<h2 aria-hidden="true">Hi, I'm</h2>
|
||||
<h1>Victor Westerlund</h1>
|
||||
</section>
|
||||
<hr aria-hidden="true">
|
||||
<section class="about">
|
||||
<p>I​'m a full-stack web developer from Sweden, currently working as IT-Lead at <a href="https://icellate.com">iCellate Medical</a> in Solna, Stockholm - a biopharma start-up developing precision oncology. I develop and maintain <a href="https://github.com/VictorWesterlund/vegvisir">my own web framework</a> and use it to build web apps and websites - including this one.</p>
|
||||
<p>The <programming/markup/command>-languages I currently use the most are (in a mostly accurate decending order): PHP, JavaScript, CSS, MySQL, Python, SQLite, Bash, and [raw] HTML.</p>
|
||||
</section>
|
||||
<section class="about">
|
||||
<h2>This website</h2>
|
||||
<p>This site and all of its components are 100% Free Software; licensed under the GNU GPLv3. It's built on top of my own <a href="">Vegvisir</a> (web) and <a href="">Reflect</a> (API) framework. There are no cookies or trackers on this site and analytics <strong>only</strong> consist of basic access and error logs; and from which IP address.</p>
|
||||
</section>
|
||||
<section class="about">
|
||||
<h2>Projects</h2>
|
||||
<p>These are my top projects I'm working on right now:</p>
|
||||
<p>* <a href="https://github.com/VictorWesterlund/vegvisir">Vegvisir</a>: A web framework written in PHP, for PHP developers.</p>
|
||||
<p>* <a href="https://github.com/VictorWesterlund/reflect">Reflect</a>: An API framework also written in PHP, for PHP developers.</p>
|
||||
<p>See more on my <a href="work" vv="about" vv-call="navigate">works page</a>. And even more including smaller projects on my <a href="https://github.com/VictorWesterlund">GitHub</a>.</p>
|
||||
</section>
|
||||
<section class="about">
|
||||
<h2>Personal</h2>
|
||||
<p>At times, I can become a real sucker for a <span class="interests">variety of topics I find interesting</span>, and spend hours reading as much as I can about them too. When I'm not glued to a computer screen, I like me some skiing and occasional hobby photography. I'm also a real coffeeholic.</p>
|
||||
<p>Let's work on something together, have a chat, or anything else. <a href="contact" vv="about" vv-call="navigate">write me a line!</a></p>
|
||||
</section>
|
||||
<section class="about">
|
||||
<h2>Projects</h2>
|
||||
<p>These are my top projects I'm working on right now:</p>
|
||||
<p>* <a href="">Vegvisir</a>: A web framework written in PHP, for PHP developers.</p>
|
||||
<p>* <a href="">Reflect</a>: An API framework also written in PHP, for PHP developers.</p>
|
||||
<p>See more on my <a href="work" vv="about" vv-call="navigate">works page</a>. And even more including smaller projects on my <a href="https://github.com/VictorWesterlund">GitHub</a>.</p>
|
||||
</section>
|
||||
<hr>
|
||||
<section class="version">
|
||||
<p>website version: <?= VV::include("about/version") ?></p>
|
||||
</section>
|
||||
|
||||
<div class="interests" aria-hidden="true">
|
||||
<p>practical engineering</p>
|
||||
<p>music</p>
|
||||
<p>astronomy</p>
|
||||
<p>electronics</p>
|
||||
<p>aviation</p>
|
||||
<p>marine technology</p>
|
||||
<p>typography</p>
|
||||
</div>
|
||||
<script><?= VV::js("pages/about") ?></script>
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
A pretty naive website version fetcher that assumes the latest git tag is the
|
||||
version the website is currently displaying. The intent is that any live-master
|
||||
of this website should always track the master branch and pull the latest HEAD
|
||||
without any exceptions.
|
||||
*/
|
||||
|
||||
use Vegvisir\Path;
|
||||
|
||||
// Get tags from local git folder
|
||||
$dir = scandir(Path::root(".git/refs/tags"), SCANDIR_SORT_ASCENDING);
|
||||
|
||||
// Get current version number from latest tag
|
||||
$version = $dir[2] ?? "";
|
||||
|
||||
?>
|
||||
<a href="https://github.com/victorwesterlund/vlw.se/releases/<?= $version ?>"><?= $version ?></a>
|
|
@ -1,89 +0,0 @@
|
|||
<?php
|
||||
|
||||
use VLW\API\Client;
|
||||
use VLW\API\Endpoints;
|
||||
|
||||
enum ContactFieldsEnum: string {
|
||||
case EMAIL = "email";
|
||||
case MESSAGE = "message";
|
||||
}
|
||||
|
||||
// Connect to VLW API
|
||||
$api = new Client();
|
||||
|
||||
// Null when nothing has been sent, true if message has been sent, false if it failed
|
||||
$message_sent = null;
|
||||
|
||||
// Message has been submitted
|
||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
// Submit message to endpoint and set variable with results
|
||||
$message_sent = $api->call(Endpoints::MESSAGES->value)->post([
|
||||
ContactFieldsEnum::EMAIL->value => $_POST[ContactFieldsEnum::EMAIL->value],
|
||||
ContactFieldsEnum::MESSAGE->value => $_POST[ContactFieldsEnum::MESSAGE->value]
|
||||
])->ok;
|
||||
}
|
||||
|
||||
?>
|
||||
<style><?= VV::css("pages/contact") ?></style>
|
||||
<section>
|
||||
<h1>Let's chat</h1>
|
||||
<p>The best way to get in touch is by email, or with the form on this page. The time in Sweden right now is <span></span> so I will probably reply within a few hours.</p>
|
||||
</section>
|
||||
<section class="social">
|
||||
<a href="mailto:victor@vlw.se"><social>
|
||||
<?= VV::media("icons/email.svg") ?>
|
||||
<p>e-mail</p>
|
||||
</social></a>
|
||||
<a href="https://mastodon.social/@vlwone"><social>
|
||||
<?= VV::media("icons/mastodon.svg") ?>
|
||||
<p>mastodon</p>
|
||||
</social></a>
|
||||
<a href="https://web.libera.chat/#vlw.se"><social>
|
||||
<?= VV::media("icons/libera.svg") ?>
|
||||
<p>libera.chat</p>
|
||||
</social></a>
|
||||
</section>
|
||||
<?= VV::media("line.svg") ?>
|
||||
<section class="pgp">
|
||||
<?= VV::media("icons/pin.svg") ?>
|
||||
<h3>encrypt your message with my OpenPGP key.</h3>
|
||||
<p>my key is also listed on the <a href="https://keys.openpgp.org/search?q=victor%40vlw.se" target="_blank" rel="noopener noreferer">openPGP key server</a> for victor@vlw.se so your e-mail client can automatically retreive it if supported.</p>
|
||||
<div class="buttons">
|
||||
<a href="https://keys.openpgp.org/vks/v1/by-fingerprint/DCE987311CB5D2A252F58951D0AD730E1057DFC6"><button class="solid">download ASC</button></a>
|
||||
<a href="https://emailselfdefense.fsf.org/en/" target="_blank" rel="noopener noreferer"><button>more info</button></a>
|
||||
</div>
|
||||
</section>
|
||||
<?= VV::media("line.svg") ?>
|
||||
|
||||
<?php // Show contact form if a message has not been (sucessfully) sent ?>
|
||||
<?php if ($message_sent !== true): ?>
|
||||
|
||||
<?php // Show error message if something went wrong ?>
|
||||
<?php if ($message_sent === false): ?>
|
||||
<section class="form-message error">
|
||||
<h3>😟 Oh no, something went wrong</h3>
|
||||
<p>Response from API:</p>
|
||||
<pre><?= json_encode($post_message[1], JSON_PRETTY_PRINT) ?></pre>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="form">
|
||||
<form method="POST">
|
||||
<input-group>
|
||||
<label>your email</label>
|
||||
<input type="email" name="<?= ContactFieldsEnum::EMAIL->value ?>" placeholder="nissehult@example.com" autocomplete="off"></input>
|
||||
</input-group>
|
||||
<input-group>
|
||||
<label title="this field is required">your message<sup>*</sup></label>
|
||||
<textarea name="<?= ContactFieldsEnum::MESSAGE->value ?>" required placeholder="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed molestie dignissim mauris vel dignissim. Sed et aliquet odio, id egestas libero. Vestibulum ut dui a turpis aliquam hendrerit id et dui. Morbi eu tristique quam, sit amet dictum felis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ac nibh a ex accumsan ullamcorper non quis eros. Nam at suscipit lacus. Nullam placerat semper sapien, vitae aliquet nisl elementum a. Duis viverra quam eros, eu vestibulum quam egestas sit amet. Duis lobortis varius malesuada. Mauris in fringilla mi. "></textarea>
|
||||
</input-group>
|
||||
<button class="solid">send</button>
|
||||
</form>
|
||||
</section>
|
||||
<?php else: ?>
|
||||
<section class="form-message sent">
|
||||
<h3>🙏 Message sent!</h3>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<script><?= VV::js("pages/contact") ?></script>
|
|
@ -1,6 +0,0 @@
|
|||
<style><?= VV::css("pages/error") ?></style>
|
||||
<canvas></canvas>
|
||||
<section class="error">
|
||||
<h1 glitch-text><span>4</span><span>0</span><span>4</span></h1>
|
||||
</section>
|
||||
<script type="module"><?= VV::js("pages/error") ?></script>
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
enum RGB: string {
|
||||
case WORK = "3,255,219";
|
||||
case ABOUT = "148,255,21";
|
||||
case CONTACT = "255,195,255";
|
||||
}
|
||||
|
||||
?>
|
||||
<style><?= VV::css("pages/index") ?></style>
|
||||
<div class="menu">
|
||||
<?= VV::media("line.svg") ?>
|
||||
<menu>
|
||||
<a href="/work" vv="index" vv-call="navigate"><li data-rgb="<?= RGB::WORK->value ?>" data-hue="90">work</li></a>
|
||||
<a href="/about" vv="index" vv-call="navigate"><li data-rgb="<?= RGB::ABOUT->value ?>" data-hue="390">about</li></a>
|
||||
<a href="/contact" vv="index" vv-call="navigate"><li data-rgb="<?= RGB::CONTACT->value ?>" data-hue="200">contact</li></a>
|
||||
</menu>
|
||||
<?= VV::media("line.svg") ?>
|
||||
<button class="email" vv="index" vv-call="copyEmail">
|
||||
<p>victor@vlw.se</p>
|
||||
<p class="cta">to copy</p>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<img src="/assets/media/gazing.jpg"/>
|
||||
<!--<picture class="gazing">
|
||||
<source srcset="/assets/media/gazing.avif" type="image/avif"/>
|
||||
<source srcset="/assets/media/gazing.webp" type="image/webp"/>
|
||||
<img src="/assets/media/gazing.jpg"/>
|
||||
</picture>-->
|
||||
<script><?= VV::js("pages/index") ?></script>
|
137
pages/search.php
137
pages/search.php
|
@ -1,137 +0,0 @@
|
|||
<?php
|
||||
|
||||
use VLW\API\Client;
|
||||
use VLW\API\Endpoints;
|
||||
|
||||
$api = new Client();
|
||||
|
||||
$query = $_GET["q"] ?? null;
|
||||
|
||||
// Get search results from endpoint
|
||||
$response = $api->call(Endpoints::SEARCH->value)
|
||||
// Get query string from search parameter if set
|
||||
->params(["q" => $query])
|
||||
->get();
|
||||
|
||||
?>
|
||||
<style><?= VV::css("pages/search") ?></style>
|
||||
<section class="search">
|
||||
<form method="GET">
|
||||
<search>
|
||||
<input name="q" type="text" placeholder="search anything..." value="<?= $query ?>"></input>
|
||||
</search>
|
||||
<button type="submit" class="solid">Search</button>
|
||||
</form>
|
||||
<?= VV::media("line.svg") ?>
|
||||
<button>advanced search options</button>
|
||||
</section>
|
||||
|
||||
<?php if ($response): ?>
|
||||
<?php // Get response body ?>
|
||||
<?php $body = $response->json(); ?>
|
||||
|
||||
<?php // Do things depending on the response code from API ?>
|
||||
<?php switch ($response->code): default: ?>
|
||||
<?php // An unknown error occured ?>
|
||||
<section class="error">
|
||||
<p>Something went wrong</p>
|
||||
</section>
|
||||
<?php break; ?>
|
||||
|
||||
<?php // Query was successful! (Doesn't meant we got search results tho) ?>
|
||||
<?php case 200: ?>
|
||||
|
||||
<?php // Show category sections if search matches were found ?>
|
||||
<?php if ($body["total_num_results"] > 0): ?>
|
||||
<?php // Get search results by category ?>
|
||||
<?php $categories = $body["results"]; ?>
|
||||
|
||||
<?php // Results category: work ?>
|
||||
<?php if (!empty($categories["work"])): ?>
|
||||
<section class="title work">
|
||||
<a href="/work" vv="search" vv-call="navigate"><h2>Work</h2></a>
|
||||
<p><?= count($categories["work"]) ?> search result(s) from my public work</p>
|
||||
</section>
|
||||
<section class="results work">
|
||||
|
||||
<?php // List all work category search results ?>
|
||||
<?php foreach ($categories["work"] as $result): ?>
|
||||
<div class="result">
|
||||
<h3><?= $result["title"] ?></h3>
|
||||
<p><?= $result["summary"] ?></p>
|
||||
<p><?= date(Client::DATE_FORMAT, $result["date_timestamp_created"]) ?></p>
|
||||
|
||||
<?php // Result has actions defined ?>
|
||||
<?php if (!empty($result["actions"])): ?>
|
||||
<div class="actions">
|
||||
|
||||
<?php // List all actions ?>
|
||||
<?php foreach ($result["actions"] as $action): ?>
|
||||
|
||||
<?php if (!$action["external"]): ?>
|
||||
<a href="<?= $action["href"] ?>" vv="search" vv-call="navigate"><button class="<?= $action["class_list"] ?>"><?= $action["display_text"] ?></button></a>
|
||||
<?php else: ?>
|
||||
<a href="<?= $action["href"] ?>" target="_blank"><button class="<?= $action["class_list"] ?>"><?= $action["display_text"] ?></button></a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php endforeach; ?>
|
||||
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // No search matches were found ?>
|
||||
<?php else: ?>
|
||||
<section class="empty">
|
||||
<p>No results for search term "<?= $_GET["q"] ?>"</p>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php break; ?>
|
||||
|
||||
<?php // No access to the search endpoint ?>
|
||||
<?php case 404: ?>
|
||||
<section class="error">
|
||||
<p>Connection to VLW API was successful but lacking permission to search</p>
|
||||
</section>
|
||||
<?php break; ?>
|
||||
|
||||
<?php // Got a request validation error from the endpoint ?>
|
||||
<?php case 422: ?>
|
||||
|
||||
<?php // Get all validation errors for query and list them ?>
|
||||
<?php foreach ($body["GET"]["q"] as $error_code => $error_msg): ?>
|
||||
|
||||
<?php // Check the error code of the current error ?>
|
||||
<?php switch ($error_code): default: ?>
|
||||
<section class="error">
|
||||
<p>Unknown request validation error</p>
|
||||
</section>
|
||||
<?php break; ?>
|
||||
|
||||
<?php // Search query string is not long enough ?>
|
||||
<?php case "VALUE_MIN_ERROR": ?>
|
||||
<section class="error">
|
||||
<p>Type at least <?= $error_msg ?> characters to search!</p>
|
||||
</section>
|
||||
<?php break; ?>
|
||||
|
||||
<?php endswitch; ?>
|
||||
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php break; ?>
|
||||
|
||||
<?php endswitch; ?>
|
||||
|
||||
<?php // No query search paramter set, show general information ?>
|
||||
<?php else: ?>
|
||||
<?= VV::media("icons/search.svg") ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<script><?= VV::js("pages/search") ?></script>
|
162
pages/work.php
162
pages/work.php
|
@ -1,162 +0,0 @@
|
|||
<?php
|
||||
|
||||
use VLW\API\Client;
|
||||
use VLW\API\Endpoints;
|
||||
|
||||
// Connect to VLW API
|
||||
$api = new Client();
|
||||
|
||||
// Retreive rows from work endpoint
|
||||
$response = $api->call(Endpoints::WORK->value)->get();
|
||||
|
||||
?>
|
||||
<style><?= VV::css("pages/work") ?></style>
|
||||
|
||||
<section class="git">
|
||||
<?= VV::media("icons/github.svg") ?>
|
||||
<p>Most of my free open-source software is available on GitHub and it's also mirrored on my server</p>
|
||||
<div class="buttons">
|
||||
<a href="https://github.com/victorwesterlund"><button class="solid">open GitHub</button></a>
|
||||
<a href="https://git.vlw.se"><button>mirror</button></a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php if ($response->ok): ?>
|
||||
<?php
|
||||
|
||||
/*
|
||||
Order response from endpoint into a multi-dimensional array.
|
||||
For example, a single item created at 14th of February 2024 would be ordered like this
|
||||
[2024 => [[02 => [14 => [<row_data>]]]]]
|
||||
*/
|
||||
|
||||
$rows = [];
|
||||
// Create array of arrays ordered by decending year, month, day, items
|
||||
foreach ($response->json() as $row) {
|
||||
// Create array for current year if it doesn't exist
|
||||
if (!array_key_exists($row["date_year"], $rows)) {
|
||||
$rows[$row["date_year"]] = [];
|
||||
}
|
||||
|
||||
// Create array for current month if it doesn't exist
|
||||
if (!array_key_exists($row["date_month"], $rows[$row["date_year"]])) {
|
||||
$rows[$row["date_year"]][$row["date_month"]] = [];
|
||||
}
|
||||
|
||||
// Create array for current day if it doesn't exist
|
||||
if (!array_key_exists($row["date_day"], $rows[$row["date_year"]][$row["date_month"]])) {
|
||||
$rows[$row["date_year"]][$row["date_month"]][$row["date_day"]] = [];
|
||||
}
|
||||
|
||||
// Append item to ordered array
|
||||
$rows[$row["date_year"]][$row["date_month"]][$row["date_day"]][] = $row;
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<section class="timeline">
|
||||
<?php // Get year int from key and array of months for current year ?>
|
||||
<?php foreach($rows as $year => $months): ?>
|
||||
<div class="year">
|
||||
<div class="track">
|
||||
<p><?= $year ?></p>
|
||||
</div>
|
||||
|
||||
<div class="months">
|
||||
<?php // Get month int from key and array of days for current month ?>
|
||||
<?php foreach($months as $month => $days): ?>
|
||||
<div class="month">
|
||||
<div class="track">
|
||||
<?php // Append leading zero to month ?>
|
||||
<p><?= sprintf("%02d", $month) ?></p>
|
||||
</div>
|
||||
|
||||
<div class="days">
|
||||
<?php // Get day int from key and array of items for current day ?>
|
||||
<?php foreach($days as $day => $items): ?>
|
||||
<div class="day">
|
||||
<div class="track">
|
||||
<?php // Append leading zero to day ?>
|
||||
<p><?= sprintf("%02d", $day) ?></p>
|
||||
</div>
|
||||
|
||||
<div class="items">
|
||||
<?php foreach($items as $item): ?>
|
||||
<div class="item">
|
||||
|
||||
<?php // List tags if defined for item ?>
|
||||
<?php if(!empty($item["tags"])): ?>
|
||||
<div class="tags">
|
||||
<?php foreach($item["tags"] as $tag): ?>
|
||||
<p class="tag <?= $tag["name"] ?>"><?= $tag["name"] ?></p>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // Show large heading if defined ?>
|
||||
<?php if (!empty($item["title"])): ?>
|
||||
<h2><?= $item["title"] ?></h2>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // Show cover image if defined for item ?>
|
||||
<?php if (!empty($item["cover_srcset"])): ?>
|
||||
<picture>
|
||||
|
||||
<?php // List all srcset images ?>
|
||||
<?php foreach ($item["cover_srcset"]["srcset"] as $srcset): ?>
|
||||
<?php // Skip any media that isn't an image ?>
|
||||
<?php if ($srcset["type"] !== "IMAGE"): continue; endif; ?>
|
||||
|
||||
<srcset src="/assets/media/content/<?= $srcset["id"] ?>.<?= $srcset["extension"] ?>" type="<?= $srcset["mime"] ?>"></srcset>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php
|
||||
// Get the default/fallback image for this srcset
|
||||
$default = $item["cover_srcset"]["default"];
|
||||
?>
|
||||
<img src="/assets/media/content/<?= $default["id"] ?>.<?= $default["extension"] ?>" type="<?= $default["mime"] ?>" loading="lazy"/>
|
||||
</picture>
|
||||
<?php endif; ?>
|
||||
|
||||
<p><?= $item["summary"] ?></p>
|
||||
|
||||
<?php // List actions if defined for item ?>
|
||||
<?php if(!empty($item["actions"])): ?>
|
||||
<div class="actions">
|
||||
<?php foreach($item["actions"] as $action): ?>
|
||||
<?php
|
||||
// Bind VV interactions for buttons or add new tab target if external link
|
||||
$link_attr = !$action["external"] ? "vv='work' vv-call='navigate'" : "target='_blank'";
|
||||
|
||||
// Self-reference to a work page with the item id if no href is set
|
||||
$link_href = $action["href"] === null ? "/work/{$item["id"]}" : $action["href"];
|
||||
?>
|
||||
|
||||
<a href="<?= $link_href ?>" <?= $link_attr ?>><button class="<?= $action["class_list"] ?>"><?= $action["display_text"] ?></button></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</section>
|
||||
<section class="note">
|
||||
<p>This is not really the end of the list. I will add some of my notable older work at some point.</p>
|
||||
</section>
|
||||
<?php else: ?>
|
||||
<p>Something went wrong!</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<script><?= VV::js("pages/work") ?></script>
|
59
public/about.php
Normal file
59
public/about.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<style><?= VV::css("public/assets/css/pages/about") ?></style>
|
||||
<section class="intro">
|
||||
<h2 aria-hidden="true">Hi, I'm</h2>
|
||||
<h1>Victor Westerlund</h1>
|
||||
</section>
|
||||
<hr aria-hidden="true">
|
||||
<section class="about">
|
||||
<p>I​'m a full-stack web developer from Sweden.</p>
|
||||
<p>The <programming/markup/command/query/whatever>-languages I currently use the most are (in a mostly accurate decending order): PHP, JavaScript, CSS, MySQL, TypeScript, Python, SQLite, Bash, and HTML.</p>
|
||||
</section>
|
||||
<section class="about">
|
||||
<h2>This website</h2>
|
||||
<p>This site and all of its components are <a href="https://codeberg.org/vlw/vlw.se">100% free and open source software</a>. The website is designed and built by me from the ground up using my <a href="https://vegvisir.vlw.se">web</a> and <a href="https://reflect.vlw.se">API</a> frameworks as the foundation. You will find no cookies or trackers here. The only information I have about you is your public IP-address and which resources on this site your browser requests. None of this data is used for any kind of analytics.</p>
|
||||
<p><a href="https://srv.vlw.se"><i>See detailed information about all servers/services on this domain</i></a></p>
|
||||
</section>
|
||||
<section class="about">
|
||||
<h2>Personal</h2>
|
||||
<p>Coffee, of course.. and..</p>
|
||||
<p>At times, I become a true, amateur, armchair detective for a <span class="interests">variety of your typical-nerdy topics that I find interesting</span>. And will spend a disproportionate to real-world-personal-use amount of time reading about that stuff too.</p>
|
||||
<p>Another silent passion of mine that comes out every few years is <a href="/about/battlestation">building computers</a> and fiddling with weird networking stuff.</p>
|
||||
<p>And then of course I don't mind some occational gaming, and watching movies and TV-series.</p>
|
||||
</section>
|
||||
<section class="about">
|
||||
<h2>Projects</h2>
|
||||
<p>Here are some projects I'm working on right now:</p>
|
||||
<p>* <a href="https://vegvisir.vlw.se">Vegvisir</a>: A web navigation framework for PHP.</p>
|
||||
<p>* <a href="https://reflect.vlw.se">Reflect</a>: A REST API framework for PHP developers.</p>
|
||||
<p>There is more stuff on my <a href="work">works page</a> and even more stuff on <a href="https://codeberg.org/vlw">my Codeberg profile</a>.</p>
|
||||
<p><a href="https://git.vlw.se/vlw"><i>and even EVEN more stuff on my Forgejo</i></a></p>
|
||||
</section>
|
||||
<hr>
|
||||
<section class="about">
|
||||
<h3>GitHub</h3>
|
||||
<p>I have <a href="https://giveupgithub.com" target="_blank" rel="noopener noreferer">given up GitHub</a> for their increasing number of injustices againts its users, last betrayal being GitHub's for-profit "Copilot" product which was illegaly trained on copylefted software on its platform.</p>
|
||||
<p>I signed up and started using GitHub before I became aware of how opressive to to its users and deceptive their business model is. I wasn't aware of the situation.</p>
|
||||
<p>While I am a bit skeptical to do this in case history repeats itself; [most of] <a href="https://codeberg.org/vlw">my work is now on Codeberg</a> instead. Unfortunately some things like old pull-requests, issues, and branch archives can not be migrated completely.</p>
|
||||
</section>
|
||||
<hr>
|
||||
<section>
|
||||
<p>Let's work on something together or just have a chat? <a href="contact">Write me a line!</a></p>
|
||||
</section>
|
||||
|
||||
<div class="interests" aria-hidden="true">
|
||||
<p>SSTV</p>
|
||||
<p>music</p>
|
||||
<p>aviation</p>
|
||||
<p>maritime</p>
|
||||
<p>politics</p>
|
||||
<p>astronomy</p>
|
||||
<p>typography</p>
|
||||
<p>networking</p>
|
||||
<p>electronics</p>
|
||||
<p>simulations</p>
|
||||
<p>engineering</p>
|
||||
<p>photography</p>
|
||||
<p>videography</p>
|
||||
<p>ISO 8601</p>
|
||||
</div>
|
||||
<script type="module"><?= VV::js("public/assets/js/pages/about") ?></script>
|
36
public/about/battlestation-retired.php
Normal file
36
public/about/battlestation-retired.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<style><?= VV::css("public/assets/css/pages/about/battlestation-retired") ?></style>
|
||||
<section class="title">
|
||||
<h1>Retired components</h1>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
</section>
|
||||
<section class="actions">
|
||||
<a href="<?= $_ENV["api"]["base_url"] ?>battlestation/chassis?is_retired=true" target="_blank">
|
||||
<button class="inline">Cases (API)</button>
|
||||
</a>
|
||||
<a href="<?= $_ENV["api"]["base_url"] ?>battlestation/cpu?is_retired=true" target="_blank">
|
||||
<button class="inline">CPUs (API)</button>
|
||||
</a>
|
||||
<a href="<?= $_ENV["api"]["base_url"] ?>battlestation/gpu?is_retired=true" target="_blank">
|
||||
<button class="inline">GPUs (API)</button>
|
||||
</a>
|
||||
<a href="<?= $_ENV["api"]["base_url"] ?>battlestation/mb?is_retired=true" target="_blank">
|
||||
<button class="inline">Motherboards (API)</button>
|
||||
</a>
|
||||
<a href="<?= $_ENV["api"]["base_url"] ?>battlestation/psu?is_retired=true" target="_blank">
|
||||
<button class="inline">PSUs (API)</button>
|
||||
</a>
|
||||
<a href="<?= $_ENV["api"]["base_url"] ?>battlestation/storage?is_retired=true" target="_blank">
|
||||
<button class="inline">Storage (API)</button>
|
||||
</a>
|
||||
</section>
|
||||
<section class="title">
|
||||
<h2>Found something you like?</h2>
|
||||
<p>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.</p>
|
||||
</section>
|
||||
<section class="actions">
|
||||
<a href="/contact">
|
||||
<button class="inline solid">Contact me</button>
|
||||
</a>
|
||||
</section>
|
||||
<script><?= VV::js("public/assets/js/pages/about/battlestation-retired") ?></script>
|
496
public/about/battlestation.php
Normal file
496
public/about/battlestation.php
Normal file
|
@ -0,0 +1,496 @@
|
|||
<?php
|
||||
|
||||
use Vegvisir\Path;
|
||||
|
||||
use VLW\Client\API;
|
||||
use VLW\API\Endpoints;
|
||||
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\{
|
||||
MbModel,
|
||||
CpuModel,
|
||||
GpuModel,
|
||||
PsuModel,
|
||||
DramModel,
|
||||
StorageModel,
|
||||
ChassisModel
|
||||
};
|
||||
use VLW\API\Databases\VLWdb\Models\Battlestation\Config\{
|
||||
MbPsuModel,
|
||||
MbGpuModel,
|
||||
MbDramModel,
|
||||
ConfigModel,
|
||||
MbStorageModel,
|
||||
ChassisMbModel,
|
||||
MbCpuCoolerModel,
|
||||
MbStorageSlotFormfactorEnum
|
||||
};
|
||||
|
||||
require_once VV::root("src/client/API.php");
|
||||
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");
|
||||
|
||||
// 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");
|
||||
|
||||
const GIGA = 0x3B9ACA00;
|
||||
const MEGA = 0xF4240;
|
||||
|
||||
// Connect to VLW API
|
||||
$api = new API();
|
||||
|
||||
$config = $api->call(Endpoints::BATTLESTATION->value)->get();
|
||||
|
||||
?>
|
||||
<style><?= VV::css("public/assets/css/pages/about/battlestation") ?></style>
|
||||
<?php if ($config->ok): ?>
|
||||
<section class="title">
|
||||
<h1>Battle­stations</h1>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<div>
|
||||
<a href="/about/battlestation-retired"><button class="inline solid">Retired components</button></a>
|
||||
<a href="/contact"><button class="inline">Contact me</button></a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php foreach ($config->json() as $config): ?>
|
||||
|
||||
<?php
|
||||
|
||||
// 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]
|
||||
])->get()->json()[0];
|
||||
|
||||
?>
|
||||
|
||||
<section class="heading">
|
||||
<h1><?= $config[ConfigModel::FRIENDLY_NAME->value] ?? "Lucious" ?></h1>
|
||||
<p>This rig was built: <?= date(API::DATE_FORMAT, $config[ConfigModel::DATE_BUILT->value]) ?></p>
|
||||
</section>
|
||||
<section class="config"
|
||||
data-mb="1"
|
||||
data-cpu="<?= count($motherboard["cpus"]) ?>"
|
||||
data-psu="<?= count($motherboard["psus"]) ?>"
|
||||
data-gpu="<?= count($motherboard["gpus"]) ?>"
|
||||
data-dram="<?= count($motherboard["dram"]) ?>"
|
||||
data-case="<?= count($motherboard["chassis"]) ?>"
|
||||
data-drives-mdottwo="<?= count(array_keys(array_column($motherboard["storage"], MbStorageModel::SLOT_FORMFACTOR->value), MbStorageSlotFormfactorEnum::MDOTTWO->value)) ?>"
|
||||
data-drives-twodotfive="<?= count(array_keys(array_column($motherboard["storage"], MbStorageModel::SLOT_FORMFACTOR->value), MbStorageSlotFormfactorEnum::TWODOTFIVE->value)) ?>"
|
||||
data-drives-threedotfive="<?= count(array_keys(array_column($motherboard["storage"], MbStorageModel::SLOT_FORMFACTOR->value), MbStorageSlotFormfactorEnum::THREEDOTFIVE->value)) ?>"
|
||||
>
|
||||
<?= VV::embed("public/assets/media/battlestation.svg") ?>
|
||||
<div class="specs">
|
||||
|
||||
<?php // Show motherboard details ?>
|
||||
<?php if ($motherboard): ?>
|
||||
<div data-target="mb" class="spec">
|
||||
<p>Motherboard</p>
|
||||
<h3><?= $motherboard[MbModel::VENDOR_NAME->value] ?> <span><?= $motherboard[MbModel::VENDOR_MODEL->value] ?></span></h3>
|
||||
<div>
|
||||
<div>
|
||||
<label>Formfactor</label>
|
||||
<p><?= $motherboard[MbModel::FORMFACTOR->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Brand name</label>
|
||||
<p><?= $motherboard[MbModel::VENDOR_NAME->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Brand model</label>
|
||||
<p><?= $motherboard[MbModel::VENDOR_MODEL->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>LAN</label>
|
||||
<p><?= $motherboard[MbModel::NETWORK_ETHERNET->value] ?? "No LAN" ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>WLAN</label>
|
||||
<p><?= $motherboard[MbModel::NETWORK_WLAN->value] ?? "No WLAN" ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Bluetooth</label>
|
||||
<p><?= $motherboard[MbModel::NETWORK_BLUETOOTH->value] ?? "No Bluetooth" ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Aquired</label>
|
||||
<p><?= date(API::DATE_FORMAT, $motherboard[MbModel::DATE_AQUIRED->value]) ?></p>
|
||||
</div>
|
||||
|
||||
<?php if ($motherboard[MbModel::IS_RETIRED->value]): ?>
|
||||
<div>
|
||||
<label>Retired</label>
|
||||
<p>Yes</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // List all cases (lol) ?>
|
||||
<?php foreach ($motherboard["chassis"] as $mb_chassis): ?>
|
||||
|
||||
<?php // Get case details from endpoint by id ?>
|
||||
<?php $case = $api->call(Endpoints::BATTLESTATION_CHASSIS->value)->params([
|
||||
ChassisModel::ID->value => $mb_chassis[ChassisMbModel::REF_CHASSIS_ID->value]
|
||||
])->get()->json()[0]; ?>
|
||||
|
||||
<div data-target="case" class="spec">
|
||||
<p>Case</p>
|
||||
<h3><?= $case[ChassisModel::VENDOR_NAME->value] ?> <span><?= $case[ChassisModel::VENDOR_MODEL->value] ?></span></h3>
|
||||
<div>
|
||||
<div>
|
||||
<label>Brand name</label>
|
||||
<p><?= $case[ChassisModel::VENDOR_NAME->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Brand model</label>
|
||||
<p><?= $case[ChassisModel::VENDOR_MODEL->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Nº 2.5" slots</label>
|
||||
<p><?= $case[ChassisModel::STORAGE_TWOINCHFIVE->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Nº 3.5" slots</label>
|
||||
<p><?= $case[ChassisModel::STORAGE_THREEINCHFIVE->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Aquired</label>
|
||||
<p><?= date(API::DATE_FORMAT, $case[ChassisModel::DATE_AQUIRED->value]) ?></p>
|
||||
</div>
|
||||
|
||||
<?php if ($case[ChassisModel::IS_RETIRED->value]): ?>
|
||||
<div>
|
||||
<label>Retired</label>
|
||||
<p>Yes</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php // List all CPUs ?>
|
||||
<?php foreach ($motherboard["cpus"] as $mb_cpu): ?>
|
||||
|
||||
<?php // Get case details from endpoint by id ?>
|
||||
<?php $cpu = $api->call(Endpoints::BATTLESTATION_CPU->value)->params([
|
||||
CpuModel::ID->value => $mb_cpu[MbCpuCoolerModel::REF_CPU_ID->value]
|
||||
])->get()->json()[0]; ?>
|
||||
|
||||
<div data-target="cpu" class="spec">
|
||||
<p>CPU</p>
|
||||
<h3><?= $cpu[CpuModel::VENDOR_NAME->value] ?> <span><?= $cpu[CpuModel::VENDOR_MODEL->value] ?></span></h3>
|
||||
<div>
|
||||
<div>
|
||||
<label>Brand name</label>
|
||||
<p><?= $cpu[CpuModel::VENDOR_NAME->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Brand model</label>
|
||||
<p><?= $cpu[CpuModel::VENDOR_MODEL->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Class</label>
|
||||
<p><?= $cpu[CpuModel::CPU_CLASS->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Base Clockspeed</label>
|
||||
<p><?= $cpu[CpuModel::CLOCK_BASE->value] / GIGA ?>GHz</p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Turbo Clockspeed</label>
|
||||
<p><?= $cpu[CpuModel::CLOCK_TURBO->value] / GIGA ?>GHz</p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Nº cores (P/E)</label>
|
||||
<p><?= $cpu[CpuModel::CORE_COUNT_PERFORMANCE->value] + $cpu[CpuModel::CORE_COUNT_EFFICIENCY->value] ?> (<?= $cpu[CpuModel::CORE_COUNT_PERFORMANCE->value] ?>/<?= $cpu[CpuModel::CORE_COUNT_EFFICIENCY->value] ?>)</p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Nº total threads</label>
|
||||
<p><?= $cpu[CpuModel::CORE_THREADS->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Aquired</label>
|
||||
<p><?= date(API::DATE_FORMAT, $cpu[CpuModel::DATE_AQUIRED->value]) ?></p>
|
||||
</div>
|
||||
|
||||
<?php if ($cpu[CpuModel::IS_RETIRED->value]): ?>
|
||||
<div>
|
||||
<label>Retired</label>
|
||||
<p>Yes</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<label>In motherboard slot number</label>
|
||||
<p><?= $mb_cpu[MbCpuCoolerModel::SOCKET->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Motherboard slot type</label>
|
||||
<p><?= $mb_cpu[MbCpuCoolerModel::SOCKET_TYPE->value] ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php // List all GPUs ?>
|
||||
<?php foreach ($motherboard["gpus"] as $mb_gpu): ?>
|
||||
|
||||
<?php // Get case details from endpoint by id ?>
|
||||
<?php $gpu = $api->call(Endpoints::BATTLESTATION_GPU->value)->params([
|
||||
GpuModel::ID->value => $mb_gpu[MbGpuModel::REF_GPU_ID->value]
|
||||
])->get()->json()[0]; ?>
|
||||
|
||||
<div data-target="gpu" class="spec">
|
||||
<p>GPU</p>
|
||||
<h3><?= $gpu[GpuModel::VENDOR_NAME->value] ?> <span><?= $gpu[GpuModel::VENDOR_CHIP_MODEL->value] ?></span></h3>
|
||||
<div>
|
||||
<div>
|
||||
<label>Chip brand name</label>
|
||||
<p><?= $gpu[GpuModel::VENDOR_CHIP_NAME->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Chip brand model</label>
|
||||
<p><?= $gpu[GpuModel::VENDOR_CHIP_MODEL->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>VRAM</label>
|
||||
<p><?= $gpu[GpuModel::MEMORY->value] / GIGA ?>GB</p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Brand name</label>
|
||||
<p><?= $gpu[GpuModel::VENDOR_NAME->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Brand model</label>
|
||||
<p><?= $gpu[GpuModel::VENDOR_MODEL->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Aquired</label>
|
||||
<p><?= date(API::DATE_FORMAT, $gpu[GpuModel::DATE_AQUIRED->value]) ?></p>
|
||||
</div>
|
||||
|
||||
<?php if ($gpu[GpuModel::IS_RETIRED->value]): ?>
|
||||
<div>
|
||||
<label>Retired</label>
|
||||
<p>Yes</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php // List all PSUs ?>
|
||||
<?php foreach ($motherboard["psus"] as $mb_psu): ?>
|
||||
|
||||
<?php // Get case details from endpoint by id ?>
|
||||
<?php $psu = $api->call(Endpoints::BATTLESTATION_PSU->value)->params([
|
||||
PsuModel::ID->value => $mb_psu[MbPsuModel::REF_PSU_ID->value]
|
||||
])->get()->json()[0]; ?>
|
||||
|
||||
<div data-target="psu" class="spec">
|
||||
<p>PSU</p>
|
||||
<h3><?= $psu[PsuModel::VENDOR_NAME->value] ?> <span><?= $psu[PsuModel::VENDOR_MODEL->value] ?></span> <span><?= $psu[PsuModel::POWER->value] ?>W</span></h3>
|
||||
<div>
|
||||
<div>
|
||||
<label>Power</label>
|
||||
<p><?= $psu[PsuModel::POWER->value] ?>W</p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Brand name</label>
|
||||
<p><?= $psu[PsuModel::VENDOR_NAME->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Brand model</label>
|
||||
<p><?= $psu[PsuModel::VENDOR_MODEL->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Is modular?</label>
|
||||
<p><?= $psu[PsuModel::TYPE_MODULAR->value] === "TRUE" ? "Yes" : "No" ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>80+ Rating</label>
|
||||
<p><?= $psu[PsuModel::EIGHTYPLUS_RATING->value] ?? "None" ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Aquired</label>
|
||||
<p><?= date(API::DATE_FORMAT, $psu[PsuModel::DATE_AQUIRED->value]) ?></p>
|
||||
</div>
|
||||
|
||||
<?php if ($psu[PsuModel::IS_RETIRED->value]): ?>
|
||||
<div>
|
||||
<label>Retired</label>
|
||||
<p>Yes</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div class="group">
|
||||
<p>DRAM</p>
|
||||
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
||||
</div>
|
||||
|
||||
<div class="collection">
|
||||
<?php // List all DRAM ?>
|
||||
<?php foreach ($motherboard["dram"] as $mb_dram): ?>
|
||||
|
||||
<?php // Get case details from endpoint by id ?>
|
||||
<?php $dram = $api->call(Endpoints::BATTLESTATION_DRAM->value)->params([
|
||||
DramModel::ID->value => $mb_dram[MbDramModel::REF_DRAM_ID->value]
|
||||
])->get()->json()[0]; ?>
|
||||
|
||||
<div data-target="dram" class="spec">
|
||||
<p>DRAM - <?= $dram[DramModel::TECHNOLOGY->value] ?></p>
|
||||
<h3><?= $dram[DramModel::VENDOR_NAME->value] ?>
|
||||
<span><?= $dram[DramModel::CAPACITY->value] / GIGA ?>GB</span>
|
||||
<span><?= $dram[DramModel::SPEED->value] / MEGA ?>MHz</span>
|
||||
</h3>
|
||||
<div>
|
||||
<div>
|
||||
<label>Capacity</label>
|
||||
<p><?= $dram[DramModel::CAPACITY->value] / GIGA ?>GB</p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Speed</label>
|
||||
<p><?= $dram[DramModel::SPEED->value] / MEGA ?>MHz</p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Brand name</label>
|
||||
<p><?= $dram[DramModel::VENDOR_NAME->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Brand model</label>
|
||||
<p><?= $dram[DramModel::VENDOR_MODEL->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Formfactor</label>
|
||||
<p><?= $dram[DramModel::FORMFACTOR->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Technology</label>
|
||||
<p><?= $dram[DramModel::TECHNOLOGY->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Is ECC?</label>
|
||||
<p><?= $dram[DramModel::ECC->value] === "TRUE" ? "Yes" : "No" ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Is buffered?</label>
|
||||
<p><?= $dram[DramModel::BUFFERED->value] === "TRUE" ? "Yes" : "No" ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Aquired</label>
|
||||
<p><?= date(API::DATE_FORMAT, $dram[DramModel::DATE_AQUIRED->value]) ?></p>
|
||||
</div>
|
||||
|
||||
<?php if ($dram[DramModel::IS_RETIRED->value]): ?>
|
||||
<div>
|
||||
<label>Retired</label>
|
||||
<p>Yes</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<label>In motherboard slot number</label>
|
||||
<p><?= $mb_dram[MbDramModel::SOCKET->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Motherboard slot type</label>
|
||||
<p><?= $mb_dram[MbDramModel::SOCKET_TYPE->value] ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<p>Storage</p>
|
||||
<?= VV::embed("public/assets/media/icons/chevron.svg") ?>
|
||||
</div>
|
||||
|
||||
<div class="collection">
|
||||
<?php // List all storage ?>
|
||||
<?php foreach ($motherboard["storage"] as $mb_storage): ?>
|
||||
|
||||
<?php // Get case details from endpoint by id ?>
|
||||
<?php $storage = $api->call(Endpoints::BATTLESTATION_STORAGE->value)->params([
|
||||
StorageModel::ID->value => $mb_storage[MbStorageModel::REF_STORAGE_ID->value]
|
||||
])->get()->json()[0]; ?>
|
||||
|
||||
<div data-target="drive" class="spec">
|
||||
<p><?= $storage[StorageModel::DISK_FORMFACTOR->value] ?> <?= $storage[StorageModel::DISK_TYPE->value] ?></p>
|
||||
<h3>
|
||||
<?= $storage[StorageModel::VENDOR_NAME->value] ?>
|
||||
<span><?= floor($storage[StorageModel::DISK_SIZE->value] / GIGA) ?>GB</span>
|
||||
</h3>
|
||||
<div>
|
||||
<div>
|
||||
<label>Type</label>
|
||||
<p><?= $storage[StorageModel::DISK_TYPE->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Capacity</label>
|
||||
<p><?= floor($storage[StorageModel::DISK_SIZE->value] / GIGA) ?>GB</p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Interface</label>
|
||||
<p><?= $storage[StorageModel::DISK_INTERFACE->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Formfactor</label>
|
||||
<p><?= $storage[StorageModel::DISK_FORMFACTOR->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Brand name</label>
|
||||
<p><?= $storage[StorageModel::VENDOR_NAME->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Brand model</label>
|
||||
<p><?= $storage[StorageModel::VENDOR_MODEL->value] ?></p>
|
||||
</div>
|
||||
<div>
|
||||
<label>Aquired</label>
|
||||
<p><?= date(API::DATE_FORMAT, $storage[StorageModel::DATE_AQUIRED->value]) ?></p>
|
||||
</div>
|
||||
|
||||
<?php if ($storage[StorageModel::IS_RETIRED->value]): ?>
|
||||
<div>
|
||||
<label>Retired</label>
|
||||
<p>Yes</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<label>Attatched via interface</label>
|
||||
<p><?= $mb_storage[MbStorageModel::INTERFACE->value] ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
<script type="module"><?= VV::js("public/assets/js/pages/about/battlestation") ?></script>
|
8
public/assets/css/fonts.css
Normal file
8
public/assets/css/fonts.css
Normal file
|
@ -0,0 +1,8 @@
|
|||
@font-face {
|
||||
font-family: "Roboto Mono";
|
||||
src:
|
||||
url("/assets/fonts/roboto-mono.woff2") format("woff2 supports variations"),
|
||||
url("/assets/fonts/roboto-mono.woff2") format("woff2-variations")
|
||||
;
|
||||
font-weight: 100 900;
|
||||
}
|
18
assets/css/pages/about.css → public/assets/css/pages/about.css
Executable file → Normal file
18
assets/css/pages/about.css → public/assets/css/pages/about.css
Executable file → Normal file
|
@ -5,7 +5,7 @@
|
|||
--color-accent: rgb(var(--primer-color-accent));
|
||||
}
|
||||
|
||||
main {
|
||||
vv-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--padding);
|
||||
|
@ -15,7 +15,7 @@ main {
|
|||
|
||||
/* ## Divider */
|
||||
|
||||
main > hr {
|
||||
vv-shell > hr {
|
||||
border-color: rgba(255, 255, 255, .1);
|
||||
}
|
||||
|
||||
|
@ -41,15 +41,15 @@ section.about span.interests {
|
|||
animation: interests-hue 5s infinite linear;
|
||||
}
|
||||
|
||||
/* ## Version */
|
||||
|
||||
section.version {
|
||||
color: rgba(255, 255, 255, .2);
|
||||
section.about p i:not(:hover) {
|
||||
opacity: .3;
|
||||
}
|
||||
|
||||
/* # Interests */
|
||||
|
||||
div.interests {
|
||||
--text-shadow-blur: 30px;
|
||||
|
||||
transition: 300ms opacity;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
@ -58,7 +58,7 @@ div.interests {
|
|||
height: 100%;
|
||||
font-weight: bold;
|
||||
pointer-events: none;
|
||||
font-size: 50px;
|
||||
font-size: clamp(16px, 15vw, 50px);
|
||||
color: var(--color-accent);
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
|
@ -70,9 +70,7 @@ div.interests.active {
|
|||
}
|
||||
|
||||
div.interests p {
|
||||
--text-shadow-blur: 30px;
|
||||
|
||||
transition: 300ms transform;
|
||||
transition: 500ms transform cubic-bezier(.34,0,0,.99);
|
||||
position: absolute;
|
||||
text-shadow:
|
||||
0 0 var(--text-shadow-blur) black,
|
45
public/assets/css/pages/about/battlestation-retired.css
Normal file
45
public/assets/css/pages/about/battlestation-retired.css
Normal file
|
@ -0,0 +1,45 @@
|
|||
/* # Overrides */
|
||||
|
||||
:root {
|
||||
--primer-color-accent: 148, 255, 21;
|
||||
--color-accent: rgb(var(--primer-color-accent));
|
||||
}
|
||||
|
||||
vv-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--padding);
|
||||
}
|
||||
|
||||
/* # Content */
|
||||
|
||||
/* ## Title */
|
||||
|
||||
section.title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
/* ## Actions */
|
||||
|
||||
section.actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--padding);
|
||||
}
|
||||
|
||||
/* # Size quries */
|
||||
|
||||
@media (max-width: 800px) {
|
||||
section.actions {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
section.actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
290
public/assets/css/pages/about/battlestation.css
Normal file
290
public/assets/css/pages/about/battlestation.css
Normal file
|
@ -0,0 +1,290 @@
|
|||
/* # Overrides */
|
||||
|
||||
:root {
|
||||
--primer-color-accent: 148, 255, 21;
|
||||
--color-accent: rgb(var(--primer-color-accent));
|
||||
}
|
||||
|
||||
vv-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--padding);
|
||||
}
|
||||
|
||||
/* # Content */
|
||||
|
||||
/* ## Title */
|
||||
|
||||
section.title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
padding: calc(var(--padding) * 1.5);
|
||||
background-color: rgba(var(--primer-color-accent), .1);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
section.title > 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;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue