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": {
"local/api.endpoints": "1.0.0-dev",
"reflect/plugin-rules": "^1.5",
"victorwesterlund/xenum": "^1.1"
"victorwesterlund/xenum": "dev-master",
"victorwesterlund/libmysqldriver": "dev-master"
},
"minimum-stability": "dev",
"repositories": [
{
"type": "path",
"url": "src/packages/Endpoints"
}
]
"minimum-stability": "dev"
}

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",
"This file is @generated automatically"
],
"content-hash": "9da96ba90ef20d885034442b30dce0a3",
"content-hash": "13ba5cc60bab24ac8ef5b1018fe4249b",
"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",
@ -68,9 +43,47 @@
},
"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",
"version": "1.1.1",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/VictorWesterlund/php-xenum.git",
@ -82,6 +95,7 @@
"reference": "8972f06f42abd1f382807a67e937d5564bb89699",
"shasum": ""
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
@ -110,7 +124,8 @@
"aliases": [],
"minimum-stability": "dev",
"stability-flags": {
"local/api.endpoints": 20
"victorwesterlund/xenum": 20,
"victorwesterlund/libmysqldriver": 20
},
"prefer-stable": false,
"prefer-lowest": false,

View file

@ -16,7 +16,6 @@
protected Ruleset $ruleset;
public function __construct() {
parent::__construct();
$this->ruleset = new Ruleset(strict: true);
$this->ruleset->POST([
@ -31,6 +30,8 @@
->min(1)
->max(parent::MYSQL_TEXT_MAX_LENGTH)
]);
parent::__construct($this->ruleset);
}
public function main(): Response {
@ -40,7 +41,7 @@
$entity[MessagesModel::ID->value] = parent::gen_uuid4();
$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("Failed to create message", 500);
}

View file

@ -11,11 +11,10 @@
use VLW\API\Databases\VLWdb\VLWdb;
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 +22,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($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);
}
}

View file

@ -10,7 +10,7 @@
use VLW\API\Databases\VLWdb\Models\Work\WorkModel;
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 {
protected Ruleset $ruleset;
@ -26,12 +26,10 @@
(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))
@ -57,14 +55,34 @@
}
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)
->where($_GET)
->where($filters)
->select([
WorkModel::ID->value,
WorkModel::TITLE->value,
WorkModel::SUMMARY->value,
WorkModel::IS_LISTABLE->value,
WorkModel::IS_READABLE->value,
WorkModel::DATE_YEAR->value,
WorkModel::DATE_MONTH->value,
WorkModel::DATE_DAY->value,
WorkModel::DATE_MODIFIED->value,
WorkModel::DATE_CREATED->value
]);

View file

@ -10,7 +10,7 @@
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;

View file

@ -7,7 +7,10 @@
use ReflectRules\Ruleset;
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/models/Work/WorkTags.php");
@ -31,11 +34,11 @@
}
public function main(): Response {
$response = $this->db->for(FieldsEnumsModel::TABLE)
$response = $this->db->for(WorkTagsModel::TABLE)
->where($_GET)
->select([
FieldsEnumsModel::REF_WORK_ID->value,
FieldsEnumsModel::NAME->value
WorkTagsModel::REF_WORK_ID->value,
WorkTagsModel::NAME->value
]);
return $response->num_rows > 0

View file

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

View file

@ -5,11 +5,11 @@
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";
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";
}

View file

@ -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
View file

@ -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,

View file

@ -1,15 +1,19 @@
<?php
use VLW\API\Client;
use Vegvisir\Path;
use VLW\Client\API;
use VLW\API\Endpoints;
enum ContactFieldsEnum: string {
case EMAIL = "email";
case MESSAGE = "message";
}
use VLW\API\Databases\VLWdb\Models\Messages\MessagesModel;
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
$api = new Client();
$api = new API();
?>
<style><?= VV::css("pages/contact") ?></style>
@ -50,8 +54,8 @@
// Send message via API
$send = $api->call(Endpoints::MESSAGES->value)->post([
ContactFieldsEnum::EMAIL->value => $_POST[ContactFieldsEnum::EMAIL->value],
ContactFieldsEnum::MESSAGE->value => $_POST[ContactFieldsEnum::MESSAGE->value]
MessagesModel::EMAIL->value => $_POST[MessagesModel::EMAIL->value],
MessagesModel::MESSAGE->value => $_POST[MessagesModel::MESSAGE->value]
]);
?>
@ -74,11 +78,11 @@
<form method="POST">
<input-group>
<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>
<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>
<button class="inline solid">send</button>
</form>

View file

@ -1,24 +1,32 @@
<?php
use VLW\API\Client;
use Vegvisir\Path;
use VLW\Client\API;
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
$response = $api->call(Endpoints::SEARCH->value)
// Get query string from search parameter if set
->params(["q" => $query])
->get();
$response = $api->call(Endpoints::SEARCH->value)->params([SEARCH_PARAM => $_GET[SEARCH_PARAM]])->get();
?>
<style><?= VV::css("pages/search") ?></style>
<section class="search">
<form method="GET">
<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>
<button type="submit" class="inline solid">Search</button>
</form>
@ -26,114 +34,60 @@
<button class="inline">advanced search options</button>
</section>
<?php if ($response): ?>
<?php if ($response->ok): ?>
<?php // Get response body ?>
<?php $body = $response->json(); ?>
<?php $results = $response->json(); ?>
<?php // Do things depending on the response code from API ?>
<?php switch ($response->code): default: ?>
<?php // An unknown error occured ?>
<section class="info">
<p>Something went wrong</p>
</section>
<?php break; ?>
<?php // Search contains results from the work endpoint ?>
<?php if ($results[Endpoints::WORK->value]): ?>
<section class="title work">
<a href="<? Endpoints::WORK->value ?>" vv="search" vv-call="navigate"><h2>Work</h2></a>
<p><?= count($results[Endpoints::WORK->value]) ?> search result(s) from my public work</p>
</section>
<section class="results work">
<?php // Query was successful! (Doesn't meant we got search results tho) ?>
<?php case 200: ?>
<?php // List all work category search results ?>
<?php foreach ($results[Endpoints::WORK->value] as $result): ?>
<div class="result">
<h3><?= $result[WorkModel::TITLE->value] ?></h3>
<p><?= $result[WorkModel::SUMMARY->value] ?></p>
<p><?= date(API::DATE_FORMAT, $result[WorkModel::DATE_CREATED->value]) ?></p>
<?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 // Result has actions defined ?>
<?php /*if (!empty($result["actions"])): ?>
<div class="actions">
<?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 actions ?>
<?php foreach ($result["actions"] as $action): ?>
<?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="inline <?= $action["class_list"] ?>"><?= $action["display_text"] ?></button></a>
<?php else: ?>
<a href="<?= $action["href"] ?>" target="_blank"><button class="inline <?= $action["class_list"] ?>"><?= $action["display_text"] ?></button></a>
<?php endif; ?>
<?php endforeach; ?>
</div>
<?php if (!$action["external"]): ?>
<a href="<?= $action["href"] ?>" vv="search" vv-call="navigate"><button class="inline <?= $action["class_list"] ?>"><?= $action["display_text"] ?></button></a>
<?php else: ?>
<a href="<?= $action["href"] ?>" target="_blank"><button class="inline <?= $action["class_list"] ?>"><?= $action["display_text"] ?></button></a>
<?php endif; ?>
</div>
<?php endforeach; ?>
</section>
<?php endif; ?>
<?php // No search matches were found ?>
<?php else: ?>
<section class="info noresults">
<img src="/assets/media/travolta.gif" alt="">
<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="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; ?>
</div>
<?php endif;*/ ?>
</div>
<?php endforeach; ?>
<?php break; ?>
<?php endswitch; ?>
<?php // No query search paramter set, show general information ?>
</section>
<?php endif; ?>
<?php else: ?>
<?= VV::media("icons/search.svg") ?>
<?php if (!empty($_GET[SEARCH_PARAM])): ?>
<section class="info noresults">
<img src="/assets/media/travolta.gif" alt="">
<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>
<?php endif; ?>
<?php endif; ?>
<script><?= VV::js("pages/search") ?></script>

View file

@ -1,13 +1,32 @@
<?php
use VLW\API\Client;
use Vegvisir\Path;
use VLW\Client\API;
use VLW\API\Endpoints;
// Connect to VLW API
$api = new Client();
use VLW\API\Databases\VLWdb\Models\Work\WorkModel;
use VLW\API\Databases\VLWdb\Models\Work\WorkTagsModel;
use VLW\API\Databases\VLWdb\Models\Work\WorkActionsModel;
// Retreive rows from work endpoint
$response = $api->call(Endpoints::WORK->value)->get();
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");
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>
@ -21,7 +40,7 @@
</div>
</section>
<?php if ($response->ok): ?>
<?php if ($resp_work->ok): ?>
<?php
/*
@ -32,24 +51,24 @@
$rows = [];
// 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
if (!array_key_exists($row["date_year"], $rows)) {
$rows[$row["date_year"]] = [];
if (!array_key_exists($row[WorkModel::DATE_YEAR->value], $rows)) {
$rows[$row[WorkModel::DATE_YEAR->value]] = [];
}
// 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"]] = [];
if (!array_key_exists($row[WorkModel::DATE_MONTH->value], $rows[$row[WorkModel::DATE_YEAR->value]])) {
$rows[$row[WorkModel::DATE_YEAR->value]][$row[WorkModel::DATE_MONTH->value]] = [];
}
// 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"]] = [];
if (!array_key_exists($row[WorkModel::DATE_DAY->value], $rows[$row[WorkModel::DATE_YEAR->value]][$row[WorkModel::DATE_MONTH->value]])) {
$rows[$row[WorkModel::DATE_YEAR->value]][$row[WorkModel::DATE_MONTH->value]][$row[WorkModel::DATE_DAY->value]] = [];
}
// 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): ?>
<div class="item">
<?php // List tags if defined for item ?>
<?php if(!empty($item["tags"])): ?>
<?php // Get array index ids from tags array where work entity id matches ref_work_id ?>
<?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">
<?php foreach($item["tags"] as $tag): ?>
<p class="tag <?= $tag["name"] ?>"><?= $tag["name"] ?></p>
<?php foreach($tag_ids as $tag_id): ?>
<?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; ?>
</div>
<?php endif; ?>
<?php // Show large heading if defined ?>
<?php if (!empty($item["title"])): ?>
<h2><?= $item["title"] ?></h2>
<?php if (!empty($item[WorkModel::TITLE->value])): ?>
<h2><?= $item[WorkModel::TITLE->value] ?></h2>
<?php endif; ?>
<?php // Show cover image if defined for item ?>
<?php if (!empty($item["cover_srcset"])): ?>
<picture>
<p><?= $item[WorkModel::SUMMARY->value] ?></p>
<?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 // Get array index ids from actions array where work entity id matches ref_work_id ?>
<?php $action_ids = array_keys(array_column($work_actions, WorkTagsModel::REF_WORK_ID->value), $item[WorkModel::ID->value]); ?>
<?php // List actions if defined for item ?>
<?php if(!empty($item["actions"])): ?>
<?php if($action_ids): ?>
<div class="actions">
<?php foreach($item["actions"] as $action): ?>
<?php foreach($action_ids as $action_id): ?>
<?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'";
// Get tag details from tag array by index id
$action = $work_actions[$action_id];
// 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"];
$link_attr = !$action[WorkActionsModel::EXTERNAL->value]
// 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>

View file

@ -1,10 +1,11 @@
<?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
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"
}