wip: 2024-06-16T13:05:34+0200 (1718535934)

This commit is contained in:
Victor Westerlund 2024-06-16 14:56:52 +02:00
parent 4b1fbf331c
commit 773fae62c1
19 changed files with 429 additions and 471 deletions

View file

@ -1,14 +1,8 @@
{ {
"require": { "require": {
"local/api.endpoints": "1.0.0-dev",
"reflect/plugin-rules": "^1.5", "reflect/plugin-rules": "^1.5",
"victorwesterlund/xenum": "^1.1" "victorwesterlund/xenum": "dev-master",
"victorwesterlund/libmysqldriver": "dev-master"
}, },
"minimum-stability": "dev", "minimum-stability": "dev"
"repositories": [
{
"type": "path",
"url": "src/packages/Endpoints"
}
]
} }

71
api/composer.lock generated
View file

@ -4,33 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "9da96ba90ef20d885034442b30dce0a3", "content-hash": "13ba5cc60bab24ac8ef5b1018fe4249b",
"packages": [ "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", "name": "reflect/plugin-rules",
"version": "1.5.0", "version": "1.5.0",
@ -68,9 +43,47 @@
}, },
"time": "2024-01-17T11:07:44+00:00" "time": "2024-01-17T11:07:44+00:00"
}, },
{
"name": "victorwesterlund/libmysqldriver",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/VictorWesterlund/php-libmysqldriver.git",
"reference": "adc2fda90a3b8308e8a9df202d5ec418a9220ff8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/VictorWesterlund/php-libmysqldriver/zipball/adc2fda90a3b8308e8a9df202d5ec418a9220ff8",
"reference": "adc2fda90a3b8308e8a9df202d5ec418a9220ff8",
"shasum": ""
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
"libmysqldriver\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-3.0-only"
],
"authors": [
{
"name": "Victor Westerlund",
"email": "victor.vesterlund@gmail.com"
}
],
"description": "Abstraction library for common mysqli features",
"support": {
"issues": "https://github.com/VictorWesterlund/php-libmysqldriver/issues",
"source": "https://github.com/VictorWesterlund/php-libmysqldriver/tree/3.6.1"
},
"time": "2024-04-29T08:17:12+00:00"
},
{ {
"name": "victorwesterlund/xenum", "name": "victorwesterlund/xenum",
"version": "1.1.1", "version": "dev-master",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/VictorWesterlund/php-xenum.git", "url": "https://github.com/VictorWesterlund/php-xenum.git",
@ -82,6 +95,7 @@
"reference": "8972f06f42abd1f382807a67e937d5564bb89699", "reference": "8972f06f42abd1f382807a67e937d5564bb89699",
"shasum": "" "shasum": ""
}, },
"default-branch": true,
"type": "library", "type": "library",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@ -110,7 +124,8 @@
"aliases": [], "aliases": [],
"minimum-stability": "dev", "minimum-stability": "dev",
"stability-flags": { "stability-flags": {
"local/api.endpoints": 20 "victorwesterlund/xenum": 20,
"victorwesterlund/libmysqldriver": 20
}, },
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,

View file

@ -16,7 +16,6 @@
protected Ruleset $ruleset; protected Ruleset $ruleset;
public function __construct() { public function __construct() {
parent::__construct();
$this->ruleset = new Ruleset(strict: true); $this->ruleset = new Ruleset(strict: true);
$this->ruleset->POST([ $this->ruleset->POST([
@ -31,6 +30,8 @@
->min(1) ->min(1)
->max(parent::MYSQL_TEXT_MAX_LENGTH) ->max(parent::MYSQL_TEXT_MAX_LENGTH)
]); ]);
parent::__construct($this->ruleset);
} }
public function main(): Response { public function main(): Response {
@ -40,7 +41,7 @@
$entity[MessagesModel::ID->value] = parent::gen_uuid4(); $entity[MessagesModel::ID->value] = parent::gen_uuid4();
$entity[MessagesModel::DATE_CREATED->value] = time(); $entity[MessagesModel::DATE_CREATED->value] = time();
return $this->db->for(MessagesModel::TABLE)->insert($_POST) === true return $this->db->for(MessagesModel::TABLE)->insert($entity) === true
? new Response($entity[MessagesModel::ID->value], 201) ? new Response($entity[MessagesModel::ID->value], 201)
: new Response("Failed to create message", 500); : new Response("Failed to create message", 500);
} }

View file

@ -11,11 +11,10 @@
use VLW\API\Databases\VLWdb\VLWdb; use VLW\API\Databases\VLWdb\VLWdb;
use VLW\API\Databases\VLWdb\Models\Work\WorkModel; 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/VLWdb.php");
require_once Path::root("src/databases/models/Work.php"); require_once Path::root("src/databases/models/Work/Work.php");
require_once Path::root("src/databases/models/WorkActions.php");
class GET_Search extends VLWdb { class GET_Search extends VLWdb {
const GET_QUERY = "q"; const GET_QUERY = "q";
@ -23,118 +22,35 @@
protected Ruleset $ruleset; protected Ruleset $ruleset;
public function __construct() { public function __construct() {
parent::__construct();
$this->ruleset = new Ruleset(strict: true); $this->ruleset = new Ruleset(strict: true);
$this->ruleset->GET([ $this->ruleset->GET([
(new Rules(self::GET_QUERY)) (new Rules(self::GET_QUERY))
->required() ->required()
->type(Type::STRING) ->type(Type::STRING)
->min(2) ->min(1)
->max(parent::MYSQL_VARCHAR_MAX_LENGTH) ->max(parent::MYSQL_VARCHAR_MAX_LENGTH)
]); ]);
parent::__construct($this->ruleset);
} }
// Return an SQL string from array for use in prepared statements private function search_work(): Response {
private static function array_to_wildcard_sql(array $columns): string { return (new Call(Endpoints::WORK->value))->params([
$sql = array_map(fn(string $column): string => "{$column} LIKE CONCAT('%', ?, '%')", $columns); WorkModel::TITLE->value => $_GET[self::GET_QUERY],
WorkModel::SUMMARY->value => $_GET[self::GET_QUERY]
return implode(" OR ", $sql); ])->get();
}
// 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);
} }
public function main(): Response { public function main(): Response {
// Bail out if request validation failed $results = [
if (!$this->ruleset->is_valid()) { Endpoints::WORK->value => $this->search_work()->output()
return $this->resp_rules_invalid();
}
// Get search results for each category
$categories = [
WorkModel::TABLE => $this->search_work()
]; ];
// Count total number of results from all categories // Calculate the total number of results from all searched endpoints
$total_num_results = 0; $num_results = array_sum(array_map(fn(array $result): int => count($result), array_values($results)));
foreach (array_values($categories) as $results) {
$total_num_results += count($results);
}
return new Response([ // Return 404 if no search results
"query" => $_GET[self::GET_QUERY], return new Response($results, $num_results > 0 ? 200 : 404);
"results" => $categories,
"total_num_results" => $total_num_results
]);
} }
} }

View file

@ -10,7 +10,7 @@
use VLW\API\Databases\VLWdb\Models\Work\WorkModel; use VLW\API\Databases\VLWdb\Models\Work\WorkModel;
require_once Path::root("src/databases/VLWdb.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/Work/Work.php");
class GET_Work extends VLWdb { class GET_Work extends VLWdb {
protected Ruleset $ruleset; protected Ruleset $ruleset;
@ -26,12 +26,10 @@
(new Rules(WorkModel::TITLE->value)) (new Rules(WorkModel::TITLE->value))
->type(Type::STRING) ->type(Type::STRING)
->min(3)
->max(parent::MYSQL_VARCHAR_MAX_LENGTH), ->max(parent::MYSQL_VARCHAR_MAX_LENGTH),
(new Rules(WorkModel::SUMMARY->value)) (new Rules(WorkModel::SUMMARY->value))
->type(Type::STRING) ->type(Type::STRING)
->min(1)
->max(parent::MYSQL_TEXT_MAX_LENGTH), ->max(parent::MYSQL_TEXT_MAX_LENGTH),
(new Rules(WorkModel::IS_LISTABLE->value)) (new Rules(WorkModel::IS_LISTABLE->value))
@ -57,14 +55,34 @@
} }
public function main(): Response { public function main(): Response {
// 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]}%"
];
}
// Do a wildcard search on the summary column if provided
if (array_key_exists(WorkModel::SUMMARY->value, $_GET)) {
$filters[WorkModel::SUMMARY->value] = [
"LIKE" => "%{$_GET[WorkModel::SUMMARY->value]}%"
];
}
$response = $this->db->for(WorkModel::TABLE) $response = $this->db->for(WorkModel::TABLE)
->where($_GET) ->where($filters)
->select([ ->select([
WorkModel::ID->value, WorkModel::ID->value,
WorkModel::TITLE->value, WorkModel::TITLE->value,
WorkModel::SUMMARY->value, WorkModel::SUMMARY->value,
WorkModel::IS_LISTABLE->value, WorkModel::IS_LISTABLE->value,
WorkModel::IS_READABLE->value, WorkModel::IS_READABLE->value,
WorkModel::DATE_YEAR->value,
WorkModel::DATE_MONTH->value,
WorkModel::DATE_DAY->value,
WorkModel::DATE_MODIFIED->value, WorkModel::DATE_MODIFIED->value,
WorkModel::DATE_CREATED->value WorkModel::DATE_CREATED->value
]); ]);

View file

@ -10,7 +10,7 @@
use VLW\API\Databases\VLWdb\Models\Work\WorkActionsModel; use VLW\API\Databases\VLWdb\Models\Work\WorkActionsModel;
require_once Path::root("src/databases/VLWdb.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/WorkActions.php");
class GET_WorkActions extends VLWdb { class GET_WorkActions extends VLWdb {
protected Ruleset $ruleset; protected Ruleset $ruleset;

View file

@ -7,7 +7,10 @@
use ReflectRules\Ruleset; use ReflectRules\Ruleset;
use VLW\API\Databases\VLWdb\VLWdb; use VLW\API\Databases\VLWdb\VLWdb;
use VLW\API\Databases\VLWdb\Models\Work\WorkTagsModel; use VLW\API\Databases\VLWdb\Models\Work\{
WorkTagsModel,
WorkTagsNameEnum
};
require_once Path::root("src/databases/VLWdb.php"); require_once Path::root("src/databases/VLWdb.php");
require_once Path::root("src/databases/models/Work/WorkTags.php"); require_once Path::root("src/databases/models/Work/WorkTags.php");
@ -31,11 +34,11 @@
} }
public function main(): Response { public function main(): Response {
$response = $this->db->for(FieldsEnumsModel::TABLE) $response = $this->db->for(WorkTagsModel::TABLE)
->where($_GET) ->where($_GET)
->select([ ->select([
FieldsEnumsModel::REF_WORK_ID->value, WorkTagsModel::REF_WORK_ID->value,
FieldsEnumsModel::NAME->value WorkTagsModel::NAME->value
]); ]);
return $response->num_rows > 0 return $response->num_rows > 0

View file

@ -7,5 +7,11 @@
// Enum of all available VLW endpoints grouped by category // Enum of all available VLW endpoints grouped by category
enum Endpoints: string { enum Endpoints: string {
case SEARCH = "/search";
case MESSAGES = "/messages";
case WORK = "/work"; case WORK = "/work";
case WORK_TAGS = "/work/tags";
case WORK_ACTIONS = "/work/actions";
} }

View file

@ -11,5 +11,5 @@
case IS_READ = "is_read"; case IS_READ = "is_read";
case IS_SPAM = "is_spam"; case IS_SPAM = "is_spam";
case IS_SAVED = "is_saved"; case IS_SAVED = "is_saved";
case DATE_TIMESTAMP_CREATED = "date_timestamp_created"; case DATE_CREATED = "date_created";
} }

View file

@ -1,17 +1,7 @@
{ {
"require": { "require": {
"local/api.client": "1.0.0-dev", "reflect/client": "dev-master",
"local/api.endpoints": "1.0.0-dev" "victorwesterlund/xenum": "dev-master"
}, },
"minimum-stability": "dev", "minimum-stability": "dev"
"repositories": [
{
"type": "path",
"url": "src/packages/API"
},
{
"type": "path",
"url": "api/src/packages/Endpoints"
}
]
} }

100
composer.lock generated
View file

@ -4,64 +4,11 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "73a61bf0308871f9dc9ad050aedfe13e", "content-hash": "2a8a06dc452a4eb9055d238f771dcd37",
"packages": [ "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", "name": "reflect/client",
"version": "3.0.6", "version": "dev-master",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/VictorWesterlund/reflect-client-php.git", "url": "https://github.com/VictorWesterlund/reflect-client-php.git",
@ -73,6 +20,7 @@
"reference": "89a8c041044c8c60cefafc4716d5d61b96c43e06", "reference": "89a8c041044c8c60cefafc4716d5d61b96c43e06",
"shasum": "" "shasum": ""
}, },
"default-branch": true,
"type": "library", "type": "library",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@ -95,14 +43,52 @@
"source": "https://github.com/VictorWesterlund/reflect-client-php/tree/3.0.6" "source": "https://github.com/VictorWesterlund/reflect-client-php/tree/3.0.6"
}, },
"time": "2024-04-06T14:55:04+00:00" "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": [], "packages-dev": [],
"aliases": [], "aliases": [],
"minimum-stability": "dev", "minimum-stability": "dev",
"stability-flags": { "stability-flags": {
"local/api.client": 20, "reflect/client": 20,
"local/api.endpoints": 20 "victorwesterlund/xenum": 20
}, },
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,

View file

@ -1,15 +1,19 @@
<?php <?php
use VLW\API\Client; use Vegvisir\Path;
use VLW\Client\API;
use VLW\API\Endpoints; use VLW\API\Endpoints;
enum ContactFieldsEnum: string { use VLW\API\Databases\VLWdb\Models\Messages\MessagesModel;
case EMAIL = "email";
case MESSAGE = "message"; require_once Path::root("src/client/API.php");
} require_once Path::root("api/src/Endpoints.php");
require_once Path::root("api/src/databases/models/Messages/Messages.php");
// Connect to VLW API // Connect to VLW API
$api = new Client(); $api = new API();
?> ?>
<style><?= VV::css("pages/contact") ?></style> <style><?= VV::css("pages/contact") ?></style>
@ -50,8 +54,8 @@
// Send message via API // Send message via API
$send = $api->call(Endpoints::MESSAGES->value)->post([ $send = $api->call(Endpoints::MESSAGES->value)->post([
ContactFieldsEnum::EMAIL->value => $_POST[ContactFieldsEnum::EMAIL->value], MessagesModel::EMAIL->value => $_POST[MessagesModel::EMAIL->value],
ContactFieldsEnum::MESSAGE->value => $_POST[ContactFieldsEnum::MESSAGE->value] MessagesModel::MESSAGE->value => $_POST[MessagesModel::MESSAGE->value]
]); ]);
?> ?>
@ -74,11 +78,11 @@
<form method="POST"> <form method="POST">
<input-group> <input-group>
<label>your email (optional)</label> <label>your email (optional)</label>
<input type="email" name="<?= ContactFieldsEnum::EMAIL->value ?>" placeholder="nissehult@example.com" autocomplete="off"></input> <input type="email" name="<?= MessagesModel::EMAIL->value ?>" placeholder="nissehult@example.com" autocomplete="off"></input>
</input-group> </input-group>
<input-group> <input-group>
<label title="this field is required">your message (required)</label> <label title="this field is required">your message (required)</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> <textarea name="<?= MessagesModel::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> </input-group>
<button class="inline solid">send</button> <button class="inline solid">send</button>
</form> </form>

View file

@ -1,24 +1,32 @@
<?php <?php
use VLW\API\Client; use Vegvisir\Path;
use VLW\Client\API;
use VLW\API\Endpoints; use VLW\API\Endpoints;
$api = new Client(); use VLW\API\Databases\VLWdb\Models\Work\WorkModel;
$query = $_GET["q"] ?? null; require_once Path::root("src/client/API.php");
require_once Path::root("api/src/Endpoints.php");
require_once Path::root("api/src/databases/models/Work/Work.php");
// Search endpoint query paramter
const SEARCH_PARAM = "q";
// Connect to VLW API
$api = new API();
// Get search results from endpoint // Get search results from endpoint
$response = $api->call(Endpoints::SEARCH->value) $response = $api->call(Endpoints::SEARCH->value)->params([SEARCH_PARAM => $_GET[SEARCH_PARAM]])->get();
// Get query string from search parameter if set
->params(["q" => $query])
->get();
?> ?>
<style><?= VV::css("pages/search") ?></style> <style><?= VV::css("pages/search") ?></style>
<section class="search"> <section class="search">
<form method="GET"> <form method="GET">
<search> <search>
<input name="q" type="text" placeholder="search vlw.se..." value="<?= $query ?>"></input> <input name="<? SEARCH_PARAM ?>" type="text" placeholder="search vlw.se..." value="<?= $_GET[SEARCH_PARAM] ?>"></input>
</search> </search>
<button type="submit" class="inline solid">Search</button> <button type="submit" class="inline solid">Search</button>
</form> </form>
@ -26,43 +34,27 @@
<button class="inline">advanced search options</button> <button class="inline">advanced search options</button>
</section> </section>
<?php if ($response): ?> <?php if ($response->ok): ?>
<?php // Get response body ?> <?php // Get response body ?>
<?php $body = $response->json(); ?> <?php $results = $response->json(); ?>
<?php // Do things depending on the response code from API ?> <?php // Search contains results from the work endpoint ?>
<?php switch ($response->code): default: ?> <?php if ($results[Endpoints::WORK->value]): ?>
<?php // An unknown error occured ?>
<section class="info">
<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"> <section class="title work">
<a href="/work" vv="search" vv-call="navigate"><h2>Work</h2></a> <a href="<? Endpoints::WORK->value ?>" vv="search" vv-call="navigate"><h2>Work</h2></a>
<p><?= count($categories["work"]) ?> search result(s) from my public work</p> <p><?= count($results[Endpoints::WORK->value]) ?> search result(s) from my public work</p>
</section> </section>
<section class="results work"> <section class="results work">
<?php // List all work category search results ?> <?php // List all work category search results ?>
<?php foreach ($categories["work"] as $result): ?> <?php foreach ($results[Endpoints::WORK->value] as $result): ?>
<div class="result"> <div class="result">
<h3><?= $result["title"] ?></h3> <h3><?= $result[WorkModel::TITLE->value] ?></h3>
<p><?= $result["summary"] ?></p> <p><?= $result[WorkModel::SUMMARY->value] ?></p>
<p><?= date(Client::DATE_FORMAT, $result["date_timestamp_created"]) ?></p> <p><?= date(API::DATE_FORMAT, $result[WorkModel::DATE_CREATED->value]) ?></p>
<?php // Result has actions defined ?> <?php // Result has actions defined ?>
<?php if (!empty($result["actions"])): ?> <?php /*if (!empty($result["actions"])): ?>
<div class="actions"> <div class="actions">
<?php // List all actions ?> <?php // List all actions ?>
@ -77,63 +69,25 @@
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<?php endif; ?> <?php endif;*/ ?>
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>
</section> </section>
<?php endif; ?> <?php endif; ?>
<?php else: ?>
<?php // No search matches were found ?> <?php if (!empty($_GET[SEARCH_PARAM])): ?>
<?php else: ?>
<section class="info noresults"> <section class="info noresults">
<img src="/assets/media/travolta.gif" alt=""> <img src="/assets/media/travolta.gif" alt="">
<p>No results for search term "<?= $_GET["q"] ?>"</p> <p>No results for search term "<?= $_GET[SEARCH_PARAM] ?>"</p>
</section>
<?php else: ?>
<section class="info empty">
<?= VV::media("icons/search.svg") ?>
<p>Start typing to search</p>
</section> </section>
<?php endif; ?> <?php endif; ?>
<?php break; ?>
<?php // No access to the search endpoint ?>
<?php case 404: ?>
<section class="info">
<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="info">
<p>Unknown request validation error</p>
</section>
<?php break; ?>
<?php // Search query string is not long enough ?>
<?php case "VALUE_MIN_ERROR": ?>
<section class="info">
<?= VV::media("icons/search.svg") ?>
<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; ?> <?php endif; ?>
<script><?= VV::js("pages/search") ?></script> <script><?= VV::js("pages/search") ?></script>

View file

@ -1,13 +1,32 @@
<?php <?php
use VLW\API\Client; use Vegvisir\Path;
use VLW\Client\API;
use VLW\API\Endpoints; use VLW\API\Endpoints;
// Connect to VLW API use VLW\API\Databases\VLWdb\Models\Work\WorkModel;
$api = new Client(); use VLW\API\Databases\VLWdb\Models\Work\WorkTagsModel;
use VLW\API\Databases\VLWdb\Models\Work\WorkActionsModel;
// Retreive rows from work endpoint require_once Path::root("src/client/API.php");
$response = $api->call(Endpoints::WORK->value)->get(); require_once Path::root("api/src/Endpoints.php");
require_once Path::root("api/src/databases/models/Work/Work.php");
require_once Path::root("api/src/databases/models/Work/WorkTags.php");
require_once Path::root("api/src/databases/models/Work/WorkActions.php");
// Connect to VLW API
$api = new API();
// Retreive rows from work endpoints
$resp_work = $api->call(Endpoints::WORK->value)->get();
// Resolve tags and actions if we got work results
if ($resp_work->ok) {
$work_tags = $api->call(Endpoints::WORK_TAGS->value)->get()->json();
$work_actions = $api->call(Endpoints::WORK_ACTIONS->value)->get()->json();
}
?> ?>
<style><?= VV::css("pages/work") ?></style> <style><?= VV::css("pages/work") ?></style>
@ -21,7 +40,7 @@
</div> </div>
</section> </section>
<?php if ($response->ok): ?> <?php if ($resp_work->ok): ?>
<?php <?php
/* /*
@ -32,24 +51,24 @@
$rows = []; $rows = [];
// Create array of arrays ordered by decending year, month, day, items // Create array of arrays ordered by decending year, month, day, items
foreach ($response->json() as $row) { foreach ($resp_work->json() as $row) {
// Create array for current year if it doesn't exist // Create array for current year if it doesn't exist
if (!array_key_exists($row["date_year"], $rows)) { if (!array_key_exists($row[WorkModel::DATE_YEAR->value], $rows)) {
$rows[$row["date_year"]] = []; $rows[$row[WorkModel::DATE_YEAR->value]] = [];
} }
// Create array for current month if it doesn't exist // Create array for current month if it doesn't exist
if (!array_key_exists($row["date_month"], $rows[$row["date_year"]])) { if (!array_key_exists($row[WorkModel::DATE_MONTH->value], $rows[$row[WorkModel::DATE_YEAR->value]])) {
$rows[$row["date_year"]][$row["date_month"]] = []; $rows[$row[WorkModel::DATE_YEAR->value]][$row[WorkModel::DATE_MONTH->value]] = [];
} }
// Create array for current day if it doesn't exist // Create array for current day if it doesn't exist
if (!array_key_exists($row["date_day"], $rows[$row["date_year"]][$row["date_month"]])) { if (!array_key_exists($row[WorkModel::DATE_DAY->value], $rows[$row[WorkModel::DATE_YEAR->value]][$row[WorkModel::DATE_MONTH->value]])) {
$rows[$row["date_year"]][$row["date_month"]][$row["date_day"]] = []; $rows[$row[WorkModel::DATE_YEAR->value]][$row[WorkModel::DATE_MONTH->value]][$row[WorkModel::DATE_DAY->value]] = [];
} }
// Append item to ordered array // Append item to ordered array
$rows[$row["date_year"]][$row["date_month"]][$row["date_day"]][] = $row; $rows[$row[WorkModel::DATE_YEAR->value]][$row[WorkModel::DATE_MONTH->value]][$row[WorkModel::DATE_DAY->value]][] = $row;
} }
?> ?>
@ -84,52 +103,50 @@
<?php foreach($items as $item): ?> <?php foreach($items as $item): ?>
<div class="item"> <div class="item">
<?php // List tags if defined for item ?> <?php // Get array index ids from tags array where work entity id matches ref_work_id ?>
<?php if(!empty($item["tags"])): ?> <?php $tag_ids = array_keys(array_column($work_tags, WorkTagsModel::REF_WORK_ID->value), $item[WorkModel::ID->value]); ?>
<?php // List tags if available ?>
<?php if($tag_ids): ?>
<div class="tags"> <div class="tags">
<?php foreach($item["tags"] as $tag): ?> <?php foreach($tag_ids as $tag_id): ?>
<p class="tag <?= $tag["name"] ?>"><?= $tag["name"] ?></p> <?php // Get tag details from tag array by index id ?>
<?php $tag = $work_tags[$tag_id]; ?>
<p class="tag <?= $tag[WorkTagsModel::NAME->value] ?>"><?= $tag[WorkTagsModel::NAME->value] ?></p>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php // Show large heading if defined ?> <?php // Show large heading if defined ?>
<?php if (!empty($item["title"])): ?> <?php if (!empty($item[WorkModel::TITLE->value])): ?>
<h2><?= $item["title"] ?></h2> <h2><?= $item[WorkModel::TITLE->value] ?></h2>
<?php endif; ?> <?php endif; ?>
<?php // Show cover image if defined for item ?> <p><?= $item[WorkModel::SUMMARY->value] ?></p>
<?php if (!empty($item["cover_srcset"])): ?>
<picture>
<?php // List all srcset images ?> <?php // Get array index ids from actions array where work entity id matches ref_work_id ?>
<?php foreach ($item["cover_srcset"]["srcset"] as $srcset): ?> <?php $action_ids = array_keys(array_column($work_actions, WorkTagsModel::REF_WORK_ID->value), $item[WorkModel::ID->value]); ?>
<?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 // List actions if defined for item ?>
<?php if(!empty($item["actions"])): ?> <?php if($action_ids): ?>
<div class="actions"> <div class="actions">
<?php foreach($item["actions"] as $action): ?> <?php foreach($action_ids as $action_id): ?>
<?php <?php
// Bind VV interactions for buttons or add new tab target if external link // Get tag details from tag array by index id
$link_attr = !$action["external"] ? "vv='work' vv-call='navigate'" : "target='_blank'"; $action = $work_actions[$action_id];
// Self-reference to a work page with the item id if no href is set $link_attr = !$action[WorkActionsModel::EXTERNAL->value]
$link_href = $action["href"] === null ? "/work/{$item["id"]}" : $action["href"]; // Bind VV Interactions for local links
? "vv='work' vv-call='navigate'"
// Open external links in a new tab
: "target='_blank'";
$link_href = $action[WorkActionsModel::HREF->value] === null
// Navigate to work details page if no href is defined
? "/work/{$item[WorkModel::ID->value]}"
// Href is defined so use it directly
: $action[WorkActionsModel::HREF->value];
?> ?>
<a href="<?= $link_href ?>" <?= $link_attr ?>><button class="inline <?= $action["class_list"] ?>"><?= $action["display_text"] ?></button></a> <a href="<?= $link_href ?>" <?= $link_attr ?>><button class="inline <?= $action["class_list"] ?>"><?= $action["display_text"] ?></button></a>

View file

@ -1,10 +1,11 @@
<?php <?php
namespace VLW\API; namespace VLW\Client;
use Reflect\Client as ReflectClient; use Vegvisir\Path;
use Reflect\Client;
class Client extends ReflectClient { class API extends Client {
// ISO 8601: YYYY-MM-DD // ISO 8601: YYYY-MM-DD
public const DATE_FORMAT = "Y-m-d"; public const DATE_FORMAT = "Y-m-d";

57
src/entities/Entity.php Normal file
View file

@ -0,0 +1,57 @@
<?php
namespace VLW\Entities;
use Vegvisir\Path;
use Reflect\Response;
use VLW\Client\API;
use VLW\API\Endpoints;
require_once Path::root("src/client/API.php");
require_once Path::root("api/src/Endpoints.php");
interface EntityInterface {}
abstract class Entity implements EntityInterface {
public const ENTITY_ID = "id";
public readonly ?string $id;
public readonly object $entity;
public readonly Response $response;
protected readonly Api $api;
protected readonly Endpoints $endpoint;
public function __construct(Endpoints $endpoint, ?string $id) {
$this->id = $id;
$this->api = new Api();
$this->endpoint = $endpoint;
$this->resolve_entity_by_id();
}
private function resolve_entity_by_id() {
// Bail out wit a dummy Response if no id was provided
if (!$this->id) {
$this->response = new Response("", 404);
return;
}
$this->response = $this->api
->call($this->endpoint->value)
->params([self::ENTITY_ID => $this->id])
->get();
// Load response into entity object if successful
if ($this->response->ok) {
$this->entity = (object) $this->response->json()[0];
}
}
public function resolve(Endpoints $endpoint, array $params): array {
$response = $this->api->call($endpoint->value)->params($params)->get();
return $response->ok ? $response->json() : [];
}
}

74
src/entities/VLW/Work.php Normal file
View file

@ -0,0 +1,74 @@
<?php
namespace VLW\Entities\Work;
use Vegvisir\Path;
require_once Path::root("src/entities/Entity.php");
class WorkEntity extends Entity {
private array $individuals = [];
private array $signatures = [];
private array $documents = [];
private array $studies = [];
private array $notes = [];
public function __construct(string $id) {
parent::__construct(Endpoints::WORK, $id);
}
public function signatures(): array {
if ($this->signatures) {
return $this->signatures;
}
foreach ($this->resolve(Endpoints::ICELDB_ANALYSES_SIGNATURES, ["ref_analysis_id" => $this->id]) as $rel) {
$this->signatures[$rel["id"]] = $rel;
$this->signatures[$rel["id"]]["user"] = $this->resolve(Endpoints::ICELDB_USERS, ["ref_user_id" => $rel["ref_user_id"]])[0];
}
return $this->signatures;
}
public function documents(): array {
if ($this->documents) {
return $this->documents;
}
$this->documents = $this->resolve(Endpoints::ICELDB_ANALYSES_DOCUMENTS, ["ref_analysis_id" => $this->id]);
return $this->documents;
}
public function studies(): array {
if ($this->studies) {
return $this->studies;
}
foreach ($this->resolve(Endpoints::ICELDB_STUDIES_ANALYSES, ["ref_analysis_id" => $this->id]) as $rel) {
$this->studies[] = $this->resolve(Endpoints::ICELDB_STUDIES, ["id" => $rel["ref_study_id"]]);
}
return $this->studies;
}
public function notes(): array {
if ($this->notes) {
return $this->notes;
}
$this->notes = $this->resolve(Endpoints::ICELDB_ANALYSES_NOTES, ["ref_analysis_id" => $this->id]);
return $this->notes;
}
public function individuals(): array {
if ($this->individuals) {
return $this->individuals;
}
foreach ($this->resolve(Endpoints::ICELDB_ANALYSES_INDIVIDUALS, ["ref_analysis_id" => $this->id]) as $rel) {
$this->individuals[] = $this->resolve(Endpoints::ICELDB_INDIVIDUALS, ["id" => $rel["ref_individual_id"]])[0];
}
return $this->individuals;
}
}

View file

@ -1,20 +0,0 @@
{
"name": "local/api.client",
"description": "Wrapper for vlw.se API",
"type": "library",
"version": "1.0.0-dev",
"authors": [
{
"name": "Victor Westerlund",
"email": "victor@vlw.se"
}
],
"autoload": {
"psr-4": {
"VLW\\API\\": "src/"
}
},
"require": {
"reflect/client": "^3.0"
}
}

View file

@ -1,58 +0,0 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "ea73f1cfa968f06be9c2ac54d2a56c96",
"packages": [
{
"name": "reflect/client",
"version": "dev-feat/responseobj",
"source": {
"type": "git",
"url": "https://github.com/VictorWesterlund/reflect-client-php.git",
"reference": "228b3c665d4af5023ac4ed71fe32bb144521b43b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/VictorWesterlund/reflect-client-php/zipball/228b3c665d4af5023ac4ed71fe32bb144521b43b",
"reference": "228b3c665d4af5023ac4ed71fe32bb144521b43b",
"shasum": ""
},
"type": "library",
"autoload": {
"psr-4": {
"Reflect\\": "src/Reflect/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-2.0-only"
],
"authors": [
{
"name": "Victor Westerlund",
"email": "victor.vesterlund@gmail.com"
}
],
"description": "Extendable PHP interface for communicating with Reflect API over HTTP or UNIX sockets",
"support": {
"issues": "https://github.com/VictorWesterlund/reflect-client-php/issues",
"source": "https://github.com/VictorWesterlund/reflect-client-php/tree/feat/responseobj"
},
"time": "2024-03-18T11:57:57+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"reflect/client": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.0.0"
}