mirror of
https://codeberg.org/vlw/vlw.se.git
synced 2025-09-13 21:13:40 +02:00
wip: 2025-08-02T13:19:31+0200 (1754133571)
This commit is contained in:
parent
c27df3d946
commit
54c9eb39ba
13 changed files with 212 additions and 30 deletions
|
@ -5,16 +5,22 @@
|
||||||
use Reflect\Rules\{Ruleset, Rules, Type};
|
use Reflect\Rules\{Ruleset, Rules, Type};
|
||||||
|
|
||||||
use VLW\API\API;
|
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/API/API.php");
|
||||||
require_once Path::root("src/Helpers/Forgejo.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");
|
require_once Path::root("src/Helpers/GenerateTimeline.php");
|
||||||
|
|
||||||
enum ServiceEnum: string {
|
enum ServiceEnum: string {
|
||||||
use xEnum;
|
use xEnum;
|
||||||
|
|
||||||
case ALL = "all";
|
case ALL = "all";
|
||||||
|
case SEARCH = "search";
|
||||||
case FORGEJO = "forgejo";
|
case FORGEJO = "forgejo";
|
||||||
case TIMELINE = "timeline";
|
case TIMELINE = "timeline";
|
||||||
}
|
}
|
||||||
|
@ -35,6 +41,9 @@
|
||||||
case ServiceEnum::FORGEJO->value:
|
case ServiceEnum::FORGEJO->value:
|
||||||
return new Response($this->update_forgejo());
|
return new Response($this->update_forgejo());
|
||||||
|
|
||||||
|
case ServiceEnum::SEARCH->value:
|
||||||
|
return new Response($this->update_search());
|
||||||
|
|
||||||
case ServiceEnum::TIMELINE->value:
|
case ServiceEnum::TIMELINE->value:
|
||||||
return new Response($this->update_timeline());
|
return new Response($this->update_timeline());
|
||||||
|
|
||||||
|
@ -42,14 +51,19 @@
|
||||||
default:
|
default:
|
||||||
return new Response(
|
return new Response(
|
||||||
$this->update_timeline() &&
|
$this->update_timeline() &&
|
||||||
|
$this->update_search() &&
|
||||||
$this->update_forgejo()
|
$this->update_forgejo()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function update_timeline(): bool {
|
private function update_timeline(): bool {
|
||||||
return new GenerateTimeline()->generate();
|
return new GenerateTimeline()->generate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function update_search(): bool {
|
||||||
|
return new GenerateSearch()->generate();
|
||||||
|
}
|
||||||
|
|
||||||
private function update_forgejo(): bool {
|
private function update_forgejo(): bool {
|
||||||
return new Forgejo()->update();
|
return new Forgejo()->update();
|
||||||
|
|
|
@ -37,6 +37,6 @@ document.querySelector("header input[type='search']").addEventListener("input",
|
||||||
clearTimeout(event.target._throttle);
|
clearTimeout(event.target._throttle);
|
||||||
event.target._throttle = setTimeout(() => {
|
event.target._throttle = setTimeout(() => {
|
||||||
// Navigate search-results element on user input
|
// 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);
|
}, DEBOUNCE_TIMEOUT_MS);
|
||||||
});
|
});
|
|
@ -7,8 +7,9 @@
|
||||||
require_once VV::root("src/Database/Tables/Search/Search.php");
|
require_once VV::root("src/Database/Tables/Search/Search.php");
|
||||||
require_once VV::root("src/Database/Models/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 {
|
$search = new class extends Search {
|
||||||
public readonly string $query;
|
public readonly string $query;
|
||||||
|
@ -16,7 +17,7 @@
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->query = $_GET[GET_KEY_QUERY] ?? "";
|
$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>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<?php if (array_key_exists(SearchTable::QUERY->value, $_GET)): ?>
|
<?php if (isset($_GET[GET_KEY_QUERY])): ?>
|
||||||
|
|
||||||
<?php if ($search->results): ?>
|
<?php if ($search->results): ?>
|
||||||
<section class="stats">
|
<section class="stats">
|
||||||
|
@ -90,7 +91,7 @@
|
||||||
<?php case 1: ?>
|
<?php case 1: ?>
|
||||||
<section class="center">
|
<section class="center">
|
||||||
<?= VV::embed("public/assets/media/icons/search.svg") ?>
|
<?= 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>
|
</section>
|
||||||
<?php break; ?>
|
<?php break; ?>
|
||||||
|
|
||||||
|
|
|
@ -109,7 +109,7 @@
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
|
|
||||||
<?php foreach ($work->actions() as $action): ?>
|
<?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): ?>
|
<?php if ($action->icon_prepend): ?>
|
||||||
<?= VV::embed("public/assets/media/icons/" . $action->icon_prepend) ?>
|
<?= VV::embed("public/assets/media/icons/" . $action->icon_prepend) ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
2
reflect
2
reflect
|
@ -1 +1 @@
|
||||||
Subproject commit 59c45d52c1845da6b1b06ec5af2e676e327c014d
|
Subproject commit f8d45950d78a16adc64db920b5dbf59dabce5bca
|
|
@ -13,6 +13,8 @@
|
||||||
|
|
||||||
abstract public string $id { get; }
|
abstract public string $id { get; }
|
||||||
|
|
||||||
|
private static Database $_db;
|
||||||
|
|
||||||
protected readonly Database $db;
|
protected readonly Database $db;
|
||||||
private bool $_resolved = false;
|
private bool $_resolved = false;
|
||||||
private array $_row;
|
private array $_row;
|
||||||
|
@ -22,7 +24,8 @@
|
||||||
private readonly array $columns,
|
private readonly array $columns,
|
||||||
private readonly array $where
|
private readonly array $where
|
||||||
) {
|
) {
|
||||||
$this->db = new Database();
|
// Establish once and reuse Database connection
|
||||||
|
$this->db = self::$_db ??= new Database();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ?array $row {
|
private ?array $row {
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
final public static function from(Work $work): array {
|
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)
|
->from(Actions::TABLE)
|
||||||
->where([Actions::REF_WORK_ID->value => $work->id])
|
->where([Actions::REF_WORK_ID->value => $work->id])
|
||||||
->order([Actions::ORDER_IDX->value => Order::DESC])
|
->order([Actions::ORDER_IDX->value => Order::DESC])
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace VLW\Database\Models\Work;
|
namespace VLW\Database\Models\Work;
|
||||||
|
|
||||||
use \VV;
|
use \VV;
|
||||||
|
use \vlw\MySQL\Order;
|
||||||
|
|
||||||
use VLW\Helpers\UUID;
|
use VLW\Helpers\UUID;
|
||||||
use VLW\Database\Database;
|
use VLW\Database\Database;
|
||||||
|
@ -34,6 +35,11 @@
|
||||||
final public static function all(): array {
|
final public static function all(): array {
|
||||||
return array_map(fn(array $work): Timeline => new Timeline($work[TimelineTable::ID->value]), new Database()
|
return array_map(fn(array $work): Timeline => new Timeline($work[TimelineTable::ID->value]), new Database()
|
||||||
->from(TimelineTable::TABLE)
|
->from(TimelineTable::TABLE)
|
||||||
|
->order([
|
||||||
|
TimelineTable::YEAR->value => Order::DESC,
|
||||||
|
TimelineTable::MONTH->value => Order::DESC,
|
||||||
|
TimelineTable::DAY->value => Order::DESC
|
||||||
|
])
|
||||||
->select(TimelineTable::ID->value)
|
->select(TimelineTable::ID->value)
|
||||||
->fetch_all(MYSQLI_ASSOC)
|
->fetch_all(MYSQLI_ASSOC)
|
||||||
);
|
);
|
||||||
|
|
96
src/Database/Seeds/api.sql
Normal file
96
src/Database/Seeds/api.sql
Normal 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 */;
|
|
@ -13,21 +13,25 @@ CREATE TABLE `coffee` (
|
||||||
`date_created` datetime NOT NULL DEFAULT current_timestamp()
|
`date_created` datetime NOT NULL DEFAULT current_timestamp()
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
DELIMITER $$
|
DELIMITER $$
|
||||||
CREATE TRIGGER `coffee_stats_update` AFTER INSERT ON `coffee` FOR EACH ROW BEGIN
|
CREATE TRIGGER `coffee_stats_update` AFTER INSERT ON `coffee` FOR EACH ROW BEGIN
|
||||||
DECLARE count_recent INT;
|
DECLARE count_recent INT;
|
||||||
DECLARE count_average INT;
|
DECLARE count_average INT;
|
||||||
|
|
||||||
DELETE FROM coffee_stats;
|
|
||||||
|
DELETE FROM coffee_stats;
|
||||||
SELECT COUNT(*) INTO count_recent
|
|
||||||
FROM coffee
|
|
||||||
WHERE date_created > NOW() - INTERVAL 7 DAY;
|
SELECT COUNT(*) INTO count_recent
|
||||||
|
FROM coffee
|
||||||
SELECT COUNT(*) / COUNT(DISTINCT YEAR(date_created), WEEK(date_created))
|
WHERE date_created > NOW() - INTERVAL 7 DAY;
|
||||||
INTO count_average
|
|
||||||
FROM coffee;
|
|
||||||
|
SELECT COUNT(*) / COUNT(DISTINCT YEAR(date_created), WEEK(date_created))
|
||||||
INSERT INTO coffee_stats (count_week, count_week_average) VALUES (count_recent, count_average);
|
INTO count_average
|
||||||
|
FROM coffee;
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO coffee_stats (count_week, count_week_average) VALUES (count_recent, count_average);
|
||||||
END
|
END
|
||||||
$$
|
$$
|
||||||
DELIMITER ;
|
DELIMITER ;
|
||||||
|
@ -56,7 +60,7 @@ CREATE TABLE `search` (
|
||||||
`type` enum('WORK') NOT NULL,
|
`type` enum('WORK') NOT NULL,
|
||||||
`title` varchar(255) NOT NULL,
|
`title` varchar(255) NOT NULL,
|
||||||
`text` text DEFAULT NULL,
|
`text` text DEFAULT NULL,
|
||||||
`href` varchar(255) NOT NULL
|
`href` varchar(255) DEFAULT NULL
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
CREATE TABLE `work` (
|
CREATE TABLE `work` (
|
||||||
|
|
56
src/Helpers/GenerateSearch.php
Normal file
56
src/Helpers/GenerateSearch.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,8 +24,10 @@
|
||||||
$this->truncate();
|
$this->truncate();
|
||||||
|
|
||||||
foreach (Work::all() as $work) {
|
foreach (Work::all() as $work) {
|
||||||
Timeline::new($work);
|
if (!Timeline::new($work)) { return false; };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function truncate(): bool {
|
private function truncate(): bool {
|
||||||
|
|
2
vegvisir
2
vegvisir
|
@ -1 +1 @@
|
||||||
Subproject commit 1549af5be7723979b6acd5a0eda1e1ac1a70e672
|
Subproject commit 461b2cc82b268ca09919a3506625957a868a9d27
|
Loading…
Add table
Reference in a new issue