wip: 2025-08-02T13:19:31+0200 (1754133571)

This commit is contained in:
Victor Westerlund 2025-08-02 13:19:31 +02:00
parent c27df3d946
commit 54c9eb39ba
Signed by: vlw
GPG key ID: D0AD730E1057DFC6
13 changed files with 212 additions and 30 deletions

View file

@ -5,16 +5,22 @@
use Reflect\Rules\{Ruleset, Rules, Type};
use VLW\API\API;
use VLW\Helpers\{GenerateTimeline, Forgejo};
use VLW\Helpers\{
Forgejo,
GenerateSearch,
GenerateTimeline
};
require_once Path::root("src/API/API.php");
require_once Path::root("src/Helpers/Forgejo.php");
require_once Path::root("src/Helpers/GenerateSearch.php");
require_once Path::root("src/Helpers/GenerateTimeline.php");
enum ServiceEnum: string {
use xEnum;
case ALL = "all";
case SEARCH = "search";
case FORGEJO = "forgejo";
case TIMELINE = "timeline";
}
@ -35,6 +41,9 @@
case ServiceEnum::FORGEJO->value:
return new Response($this->update_forgejo());
case ServiceEnum::SEARCH->value:
return new Response($this->update_search());
case ServiceEnum::TIMELINE->value:
return new Response($this->update_timeline());
@ -42,14 +51,19 @@
default:
return new Response(
$this->update_timeline() &&
$this->update_search() &&
$this->update_forgejo()
);
}
}
private function update_timeline(): bool {
return new GenerateTimeline()->generate();
}
private function update_search(): bool {
return new GenerateSearch()->generate();
}
private function update_forgejo(): bool {
return new Forgejo()->update();

View file

@ -37,6 +37,6 @@ document.querySelector("header input[type='search']").addEventListener("input",
clearTimeout(event.target._throttle);
event.target._throttle = setTimeout(() => {
// Navigate search-results element on user input
new VV(document.querySelector("search-results")).navigate(`/search?query=${event.target.value}`);
new VV(document.querySelector("search-results")).navigate(`/search?q=${event.target.value}`);
}, DEBOUNCE_TIMEOUT_MS);
});

View file

@ -7,8 +7,9 @@
require_once VV::root("src/Database/Tables/Search/Search.php");
require_once VV::root("src/Database/Models/Search/Search.php");
const LIMIT_RESULTS = 10;
const GET_KEY_QUERY = "q";
const GET_KEY_QUERY = "q";
const LIMIT_RESULTS = 10;
const MIN_QUERY_LENGTH = 2;
$search = new class extends Search {
public readonly string $query;
@ -16,7 +17,7 @@
public function __construct() {
$this->query = $_GET[GET_KEY_QUERY] ?? "";
$this->results = parent::query($this->query, limit: LIMIT_RESULTS);
$this->results = strlen($this->query) > MIN_QUERY_LENGTH ? parent::query($this->query, limit: LIMIT_RESULTS) : [];
}
}
@ -40,7 +41,7 @@
</form>
</section>
<?php if (array_key_exists(SearchTable::QUERY->value, $_GET)): ?>
<?php if (isset($_GET[GET_KEY_QUERY])): ?>
<?php if ($search->results): ?>
<section class="stats">
@ -90,7 +91,7 @@
<?php case 1: ?>
<section class="center">
<?= VV::embed("public/assets/media/icons/search.svg") ?>
<p>Almost, type at least two letters to search</p>
<p>Almost there, type at least two letters to search</p>
</section>
<?php break; ?>

View file

@ -109,7 +109,7 @@
<div class="actions">
<?php foreach ($work->actions() as $action): ?>
<a href="<?= $action->href ?? "/work/{$work->id}" ?>"><button class="inline <?= implode(" ", $action->classlist) ?>">
<a href="<?= $action->href ?? "/work/{$work->id}" ?>"><button class="inline <?= $action->classlist ?>">
<?php if ($action->icon_prepend): ?>
<?= VV::embed("public/assets/media/icons/" . $action->icon_prepend) ?>
<?php endif; ?>

@ -1 +1 @@
Subproject commit 59c45d52c1845da6b1b06ec5af2e676e327c014d
Subproject commit f8d45950d78a16adc64db920b5dbf59dabce5bca

View file

@ -13,6 +13,8 @@
abstract public string $id { get; }
private static Database $_db;
protected readonly Database $db;
private bool $_resolved = false;
private array $_row;
@ -22,7 +24,8 @@
private readonly array $columns,
private readonly array $where
) {
$this->db = new Database();
// Establish once and reuse Database connection
$this->db = self::$_db ??= new Database();
}
private ?array $row {

View file

@ -34,7 +34,7 @@
}
final public static function from(Work $work): array {
return array_map(fn(array $tag): Actions => new Actions($tag[Actions::ID->value]), new Database()
return array_map(fn(array $tag): Action => new Action($tag[Actions::ID->value]), new Database()
->from(Actions::TABLE)
->where([Actions::REF_WORK_ID->value => $work->id])
->order([Actions::ORDER_IDX->value => Order::DESC])

View file

@ -3,6 +3,7 @@
namespace VLW\Database\Models\Work;
use \VV;
use \vlw\MySQL\Order;
use VLW\Helpers\UUID;
use VLW\Database\Database;
@ -34,6 +35,11 @@
final public static function all(): array {
return array_map(fn(array $work): Timeline => new Timeline($work[TimelineTable::ID->value]), new Database()
->from(TimelineTable::TABLE)
->order([
TimelineTable::YEAR->value => Order::DESC,
TimelineTable::MONTH->value => Order::DESC,
TimelineTable::DAY->value => Order::DESC
])
->select(TimelineTable::ID->value)
->fetch_all(MYSQLI_ASSOC)
);

View file

@ -0,0 +1,96 @@
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
CREATE TABLE `acl` (
`ref_group` varchar(255) DEFAULT NULL,
`ref_endpoint` varchar(255) NOT NULL,
`method` enum('GET','POST','PUT','PATCH','DELETE') NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
INSERT INTO `acl` (`ref_group`, `ref_endpoint`, `method`) VALUES
(NULL, 'coffee', 'GET'),
(NULL, 'languages', 'GET'),
(NULL, 'update', 'GET'),
(NULL, 'work', 'GET');
CREATE TABLE `endpoints` (
`id` varchar(255) NOT NULL,
`active` tinyint(1) UNSIGNED NOT NULL DEFAULT 1
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
INSERT INTO `endpoints` (`id`, `active`) VALUES
('coffee', 1),
('languages', 1),
('update', 1),
('work', 1);
CREATE TABLE `groups` (
`id` varchar(255) NOT NULL,
`active` tinyint(1) UNSIGNED NOT NULL DEFAULT 1,
`date_created` int(32) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `keys` (
`id` varchar(255) NOT NULL,
`active` tinyint(1) NOT NULL DEFAULT 1,
`ref_user` varchar(255) DEFAULT NULL,
`expires` int(32) DEFAULT NULL,
`created` int(32) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `rel_users_groups` (
`ref_user` varchar(255) NOT NULL,
`ref_group` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `users` (
`id` varchar(255) NOT NULL,
`active` tinyint(1) NOT NULL DEFAULT 1,
`created` int(32) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
ALTER TABLE `acl`
ADD KEY `endpoint` (`ref_endpoint`),
ADD KEY `ref_group` (`ref_group`);
ALTER TABLE `endpoints`
ADD PRIMARY KEY (`id`);
ALTER TABLE `groups`
ADD PRIMARY KEY (`id`);
ALTER TABLE `keys`
ADD PRIMARY KEY (`id`),
ADD KEY `ref_user` (`ref_user`);
ALTER TABLE `rel_users_groups`
ADD KEY `ref_user` (`ref_user`),
ADD KEY `ref_group` (`ref_group`);
ALTER TABLE `users`
ADD PRIMARY KEY (`id`);
ALTER TABLE `acl`
ADD CONSTRAINT `acl_ibfk_1` FOREIGN KEY (`ref_endpoint`) REFERENCES `endpoints` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `acl_ibfk_2` FOREIGN KEY (`ref_group`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE `keys`
ADD CONSTRAINT `keys_ibfk_1` FOREIGN KEY (`ref_user`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE `rel_users_groups`
ADD CONSTRAINT `rel_users_groups_ibfk_1` FOREIGN KEY (`ref_user`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `rel_users_groups_ibfk_2` FOREIGN KEY (`ref_group`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

View file

@ -13,21 +13,25 @@ CREATE TABLE `coffee` (
`date_created` datetime NOT NULL DEFAULT current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
DELIMITER $$
CREATE TRIGGER `coffee_stats_update` AFTER INSERT ON `coffee` FOR EACH ROW BEGIN
DECLARE count_recent INT;
DECLARE count_average INT;
DELETE FROM coffee_stats;
SELECT COUNT(*) INTO count_recent
FROM coffee
WHERE date_created > NOW() - INTERVAL 7 DAY;
SELECT COUNT(*) / COUNT(DISTINCT YEAR(date_created), WEEK(date_created))
INTO count_average
FROM coffee;
INSERT INTO coffee_stats (count_week, count_week_average) VALUES (count_recent, count_average);
CREATE TRIGGER `coffee_stats_update` AFTER INSERT ON `coffee` FOR EACH ROW BEGIN
DECLARE count_recent INT;
DECLARE count_average INT;
DELETE FROM coffee_stats;
SELECT COUNT(*) INTO count_recent
FROM coffee
WHERE date_created > NOW() - INTERVAL 7 DAY;
SELECT COUNT(*) / COUNT(DISTINCT YEAR(date_created), WEEK(date_created))
INTO count_average
FROM coffee;
INSERT INTO coffee_stats (count_week, count_week_average) VALUES (count_recent, count_average);
END
$$
DELIMITER ;
@ -56,7 +60,7 @@ CREATE TABLE `search` (
`type` enum('WORK') NOT NULL,
`title` varchar(255) NOT NULL,
`text` text DEFAULT NULL,
`href` varchar(255) NOT NULL
`href` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `work` (

View file

@ -0,0 +1,56 @@
<?php
namespace VLW\Helpers;
use \VV;
use VLW\Database\Database;
use VLW\Database\Models\Work\Work;
use VLW\Database\Models\Search\Search;
use VLW\Database\Tables\Search\{Search as SearchTable, SearchTypeEnum};
require_once VV::root("src/Database/Database.php");
require_once VV::root("src/Database/Models/Work/Work.php");
require_once VV::root("src/Database/Models/Search/Search.php");
require_once VV::root("src/Database/Tables/Search/Search.php");
class GenerateSearch {
private readonly Database $db;
public function __construct() {
$this->db = new Database();
}
public function generate(): bool {
$this->truncate();
return $this->index_work();
}
private function truncate(): bool {
return $this->db->from(SearchTable::TABLE)->delete();
}
private function index_work(): bool {
foreach (Work::all() as $work) {
// Construct a space separated fulltext query string from work entity data
$query = strtolower(implode(" ", [
$work->title,
$work->summary ?? "",
$work->date_created->format("Y"),
$work->date_created->format("n"),
$work->date_created->format("j"),
SearchTypeEnum::WORK->name
]));
$search = Search::new($query, SearchTypeEnum::WORK, $work->title);
if (!$search) { return false; }
$search->text = $work->summary;
// Use href from first Work Action if set or default to "about" page from namespace
$search->href = $work->actions() ? $work->actions()[0]->href : "/work/{$work->namespace}";
}
return true;
}
}

View file

@ -24,8 +24,10 @@
$this->truncate();
foreach (Work::all() as $work) {
Timeline::new($work);
if (!Timeline::new($work)) { return false; };
}
return true;
}
private function truncate(): bool {

@ -1 +1 @@
Subproject commit 1549af5be7723979b6acd5a0eda1e1ac1a70e672
Subproject commit 461b2cc82b268ca09919a3506625957a868a9d27