From 91500ccfe553a192012cd7acf710dbd0662d08af Mon Sep 17 00:00:00 2001 From: David Bailey Date: Mon, 24 Feb 2025 22:41:39 +0100 Subject: [PATCH 1/6] fix(search): :bug: fix lack of order_by selectability --- www/src/db_handler/mysql_handler.php | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/www/src/db_handler/mysql_handler.php b/www/src/db_handler/mysql_handler.php index dfbad78..88d14c9 100644 --- a/www/src/db_handler/mysql_handler.php +++ b/www/src/db_handler/mysql_handler.php @@ -13,6 +13,14 @@ class MySQLHandler 'title', 'view_count', 'brief', 'search_score']; CONST SQL_WRITE_COLUMNS = ['path', 'title', 'brief']; + CONST SQL_ORDER_BY_OPTIONS = [ + 'search_score' => 'post_search_score', + 'search_score_desc' => 'post_search_score DESC', + 'path' => 'post_path', + 'path_desc' => 'post_path DESC', + 'created_at' => 'post_created_at', + 'created_at_desc' => 'post_created_at DESC' + ]; private $sql_connection; private $db_prefix; @@ -398,8 +406,6 @@ class MySQLHandler $options['tags'] ??= []; } - $options['limit'] = min($options['limit'] ?? 100, 100); - // This code will take a generic user-input string, and will process it // to see if there are any special options to consider. // @@ -467,6 +473,14 @@ class MySQLHandler } $options['offset'] ??= 0; + $options['limit'] = min($options['limit'] ?? 100, 100); + + $options['order_by'] ??= 'search_score_desc'; + + if(!isset($this::SQL_ORDER_BY_OPTIONS[$options['order_by']])) { + throw new Exception("Incorrect order_by option chosen!"); + } + $qry_order_by = $this::SQL_ORDER_BY_OPTIONS[$options['order_by']]; $qry = "SELECT " . implode(', ', $qry_selects) . " @@ -474,12 +488,11 @@ class MySQLHandler LEFT JOIN {$this->db_prefix}_post_markdown AS post_markdown ON posts.post_id = post_markdown.post_id WHERE " . implode(' and ', $qry_wheres) . " - ORDER BY post_search_score DESC - LIMIT " . $options['limit'] . " - OFFSET " . $options['offset']; + ORDER BY " . $qry_order_by . " + LIMIT ? OFFSET ?"; - $search_results = $this->_exec($qry, $qry_select_types . $qry_where_types, - ...array_merge($qry_select_data, $qry_where_data))->fetch_all(MYSQLI_ASSOC); + $search_results = $this->_exec($qry, $qry_select_types . $qry_where_types . "ii", + ...array_merge($qry_select_data, $qry_where_data, [$options['limit'], $options['offset']]))->fetch_all(MYSQLI_ASSOC); $outdata = []; foreach($search_results AS $post_element) { From 5ddbae91d249d3b320401c4b918e2cb4820b6218 Mon Sep 17 00:00:00 2001 From: David Bailey Date: Mon, 24 Feb 2025 22:42:49 +0100 Subject: [PATCH 2/6] fix(templates): :bug: fix issue with Dergdown templates not being closed properly --- www/src/dergdown.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/src/dergdown.php b/www/src/dergdown.php index 1f8dbcd..c720d01 100644 --- a/www/src/dergdown.php +++ b/www/src/dergdown.php @@ -78,7 +78,8 @@ class Dergdown extends ParsedownExtra } return array( - 'markup' => $render_output + 'markup' => $render_output, + 'interrupted' => true ); } From a3604d21c9660bac95f8eaf3cbace27039f8353c Mon Sep 17 00:00:00 2001 From: David Bailey Date: Thu, 27 Mar 2025 23:31:29 +0100 Subject: [PATCH 3/6] feat(database): :sparkles: re-add per-post counters to track page views etc. --- docker_dev/mysql_schema.sql | 2 ++ www/src/db_handler/db_interface.php | 2 ++ www/src/db_handler/mysql_handler.php | 24 +++++++++++++++++++++++- www/src/db_handler/post.php | 7 ++++++- www/src/db_handler/post_handler.php | 4 ++++ www/src/serve/post.php | 21 +++++++++++++++------ www/templates/pathed_content.html | 2 +- 7 files changed, 53 insertions(+), 9 deletions(-) diff --git a/docker_dev/mysql_schema.sql b/docker_dev/mysql_schema.sql index 294266d..48f5cb2 100644 --- a/docker_dev/mysql_schema.sql +++ b/docker_dev/mysql_schema.sql @@ -26,6 +26,8 @@ CREATE TABLE dev_posts ( post_metadata JSON DEFAULT NULL, post_settings_cache JSON DEFAULT NULL, + post_counters JSON DEFAULT NULL, + PRIMARY KEY(post_id), CONSTRAINT unique_post UNIQUE (post_path), diff --git a/www/src/db_handler/db_interface.php b/www/src/db_handler/db_interface.php index 2ca64d1..c41b31a 100644 --- a/www/src/db_handler/db_interface.php +++ b/www/src/db_handler/db_interface.php @@ -73,6 +73,8 @@ interface PostdataInterface { public function set_postdata($data); public function set_post_markdown($id, $markdown); + public function increment_post_counter($path, $counter = 'views', $value = 1); + public function get_postdata($path); // Returns a key-value pair of child paths => child data public function get_post_children($path, diff --git a/www/src/db_handler/mysql_handler.php b/www/src/db_handler/mysql_handler.php index 88d14c9..105be1d 100644 --- a/www/src/db_handler/mysql_handler.php +++ b/www/src/db_handler/mysql_handler.php @@ -1,6 +1,5 @@ _exec($qry, "is", $id, $markdown); } + public function increment_post_counter($path, $counter = 'views', $value = 1) { + $path = sanitize_post_path($path); + + $qry = + "UPDATE {$this->db_prefix}_posts + SET post_counters = JSON_SET(COALESCE(post_counters, \"{}\"), ?, COALESCE(JSON_EXTRACT(post_counters, ?), 0) + ?) + WHERE post_path = ?"; + + $json_path = "$.{$counter}"; + + $this->_exec($qry, "ssds", $json_path, $json_path, $value, $path); + } + private function get_post_settings($post_path) { $post_path = sanitize_post_path($post_path); @@ -252,6 +267,13 @@ class MySQLHandler $post_settings = $this->get_post_settings($data['post_path']); } + if(isset($data['post_counters'])) { + $outdata['counters'] = json_decode($data['post_counters'], true); + } + else { + $outdata['counters'] = []; + } + $outdata = array_merge($post_settings, $post_metadata, $outdata); $outdata['host'] ??= $this->hostname; diff --git a/www/src/db_handler/post.php b/www/src/db_handler/post.php index 1b0e5bd..d26ec04 100644 --- a/www/src/db_handler/post.php +++ b/www/src/db_handler/post.php @@ -21,7 +21,8 @@ class Post implements ArrayAccess { 'title' => '404 Page', 'metadata' => [ 'type' => '404' - ] + ], + 'markdown' => 'Whoops! The dergen could not quite find that...' ]; return $post_data; @@ -139,6 +140,10 @@ class Post implements ArrayAccess { $this->data = $data; } + public function increment_counter($type = 'views', $value = 1) { + $this->handler->increment_post_counter($this->data['path'], $type, $value); + } + public function __get($name) { if($name == 'html') { return $this->get_html(); diff --git a/www/src/db_handler/post_handler.php b/www/src/db_handler/post_handler.php index 8c6ad3f..51fbbe2 100644 --- a/www/src/db_handler/post_handler.php +++ b/www/src/db_handler/post_handler.php @@ -46,6 +46,10 @@ class PostHandler { return ($this->markdown_engine)($post); } + public function increment_post_counter(...$opts) { + $this->db->increment_post_counter(...$opts); + } + public function get_children_for($post, ...$search_opts) { $child_list = $this->db->get_post_children($post->path, ...$search_opts); diff --git a/www/src/serve/post.php b/www/src/serve/post.php index 1a8aa33..46ef71c 100644 --- a/www/src/serve/post.php +++ b/www/src/serve/post.php @@ -58,7 +58,7 @@ if($REQUEST_PATH == '/upload') { render_root_template('upload.html'); die(); } -if($REQUEST_PATH == '/search') { +if($REQUEST_PATH == '/search/') { $search_results = []; $display_type = 'none'; @@ -105,13 +105,22 @@ if($REQUEST_PATH == '/search') { } $post = $adapter->get_post($REQUEST_PATH); -render_post($post); - -die(); if(!isset($post)) { - render_404(); - die(); + $error_page = $SITE_CONFIG['site_defaults']; + $error_page['path'] = '/404'; + $error_page['title'] = '404 oh no'; + $error_page['basename'] = '404.md'; + + render_root_template('derg_error.html', [ + 'page' => $error_page, + 'error_code' => '404 Hoard not found!', + 'error_description' => "Looks like we couldn't find `" . $REQUEST_PATH . "` for you. Check in later!" + ]); +} +else { + $post->increment_counter(); + render_post($post); } diff --git a/www/templates/pathed_content.html b/www/templates/pathed_content.html index 92b8bd7..c5dc10f 100644 --- a/www/templates/pathed_content.html +++ b/www/templates/pathed_content.html @@ -22,7 +22,7 @@ {%endblock%} - This page was created on {{ page.created_at }}, last edited {{ page.updated_at }}, and was viewed {{ page.view_count }} times~ + This page was created on {{ page.created_at }}, last edited {{ page.updated_at }}, and was viewed {{ page.counters.views }} times~ From 31080cae2be4fe79b8333bfa437eac53fc0baee3 Mon Sep 17 00:00:00 2001 From: David Bailey Date: Thu, 27 Mar 2025 23:32:31 +0100 Subject: [PATCH 4/6] fix: :adhesive_bandage: re-add the 404 error page --- www/templates/{old_post_types/rrror.html => derg_error.html} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename www/templates/{old_post_types/rrror.html => derg_error.html} (100%) diff --git a/www/templates/old_post_types/rrror.html b/www/templates/derg_error.html similarity index 100% rename from www/templates/old_post_types/rrror.html rename to www/templates/derg_error.html From 5e2f0a7185691fcc71a3c9fe05683186227012f5 Mon Sep 17 00:00:00 2001 From: David Bailey Date: Thu, 27 Mar 2025 23:33:27 +0100 Subject: [PATCH 5/6] feat(database): :sparkles: add completely new analytics backend --- docker_dev/mysql_schema.sql | 137 +------ www/src/db_handler/analytics_interface.php | 16 +- .../db_handler/mysql_analytics_handler.php | 334 ++++++++++++++++++ www/src/router.php | 2 + www/src/serve/api.php | 16 +- www/src/setup/analytics.php | 66 ++++ www/src/setup/db.php | 5 + 7 files changed, 446 insertions(+), 130 deletions(-) create mode 100644 www/src/db_handler/mysql_analytics_handler.php create mode 100644 www/src/setup/analytics.php diff --git a/docker_dev/mysql_schema.sql b/docker_dev/mysql_schema.sql index 48f5cb2..9585970 100644 --- a/docker_dev/mysql_schema.sql +++ b/docker_dev/mysql_schema.sql @@ -55,132 +55,25 @@ CREATE TABLE dev_post_markdown ( FULLTEXT(post_markdown) ); -CREATE TABLE path_access_counts ( - access_time DATETIME NOT NULL, - host VARCHAR(64) NOT NULL, +CREATE TABLE analytics_summations ( + time_bucket DATETIME NOT NULL, + metric VARCHAR(16) NOT NULL, + tags JSON NOT NULL, - post_path VARCHAR(255), - agent VARCHAR(255), - referrer VARCHAR(255), - - path_access_count INTEGER DEFAULT 0, - path_processing_time DOUBLE PRECISION DEFAULT 0, + metric_value DOUBLE PRECISION DEFAULT 0, - PRIMARY KEY(access_time, host, post_path, agent, referrer) + tags_md5 CHAR(32) AS (MD5(tags)), + + INDEX(time_bucket, metric), + CONSTRAINT unique_analytic UNIQUE(time_bucket, metric, tags_md5) ); -CREATE TABLE path_errcodes ( - access_timestamp DATETIME NOT NULL, - - host VARCHAR(64) NOT NULL, +CREATE TABLE analytics_events ( + event_time DATETIME NOT NULL, + metric VARCHAR(64) NOT NULL DEFAULT 'error_msg', + tags JSON NOT NULL, - post_path VARCHAR(255), - agent VARCHAR(255), - referrer VARCHAR(255), - error VARCHAR(1024), -); + event_text TEXT, -CREATE TABLE feed_cache ( - host VARCHAR(64) NOT NULL, - search_path VARCHAR(255), - export_type VARCHAR(255), - - feed_created_on DATETIME DEFAULT CURRENT_TIMESTAMP, - - feed_content MEDIUMTEXT, - - PRIMARY KEY(host, search_path, export_type) -); - -INSERT INTO posts (post_path, post_path_depth, post_metadata, post_content) -VALUES ( - '/about', - 0, -' -{ - "tags": ["test", "test2", "hellorld"], - "brief": "This is a simple test indeed", - "type": "text/markdown", - "title": "About the dergen" -} -', -' -# About the dergs indeed - -This is just a simple test. Might be nice, though! -' -), ( - '/about/neira', - 1, -' -{ - "tags": ["test", "test2", "hellorld", "neira"], - "brief": "This is a soft grab of Neira", - "type": "text/markdown", - "title": "About her" -} -', -' -# Nothing here yet! - -Sorry for this. She is working hard :> -' -), ( - '/about/xasin', - 1, -' -{ - "tags": ["test", "test2", "hellorld", "xasin"], - "brief": "This is a soft grab of Xasin", - "type": "text/markdown", - "title": "About her" -} -', -' -# Nothing here yet! - -Sorry for this. He is working hard :> -' -), ( - '/about/mesh', - 1, -' -{ - "tags": ["test", "test2", "hellorld", "mesh"], - "brief": "This is a soft grab of Mesh", - "type": "text/markdown", - "title": "About her" -} -', -' -# Nothing here yet! - -Sorry for this. Shi is working hard :> -' -), ( - '/about/alviere', - 1, -' -{ - "tags": ["test", "test2", "hellorld", "mesh"], - "brief": "SHE GRABS", - "type": "text/markdown", - "title": "SHE GRABS" -} -', -' -# Nothing here yet! - -Sorry for this. She GRABS A LOT - ----- - -## And now, for the lorem: - -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Eleifend mi in nulla posuere sollicitudin aliquam ultrices sagittis orci. Risus commodo viverra maecenas accumsan lacus vel facilisis. Sed viverra tellus in hac habitasse. Nulla malesuada pellentesque elit eget gravida cum. Posuere sollicitudin aliquam ultrices sagittis orci a. Libero nunc consequat interdum varius sit amet. Bibendum arcu vitae elementum curabitur vitae nunc sed velit. Amet mauris commodo quis imperdiet massa tincidunt nunc pulvinar. Sed adipiscing diam donec adipiscing. Laoreet id donec ultrices tincidunt arcu non sodales. Id semper risus in hendrerit gravida rutrum quisque non. Ut venenatis tellus in metus vulputate eu. - -Risus sed vulputate odio ut enim blandit volutpat. Placerat in egestas erat imperdiet. Non curabitur gravida arcu ac tortor dignissim convallis aenean. Neque aliquam vestibulum morbi blandit cursus risus at. Elementum integer enim neque volutpat ac tincidunt vitae semper. Eu ultrices vitae auctor eu augue ut. In mollis nunc sed id semper risus in hendrerit gravida. Lectus arcu bibendum at varius vel pharetra vel turpis nunc. In pellentesque massa placerat duis. Non quam lacus suspendisse faucibus. Vitae aliquet nec ullamcorper sit amet risus nullam. Accumsan lacus vel facilisis volutpat est velit egestas dui. - -Risus feugiat in ante metus dictum at tempor commodo. Duis ut diam quam nulla. Nunc aliquet bibendum enim facilisis gravida neque convallis. Tincidunt augue interdum velit euismod in pellentesque. Praesent semper feugiat nibh sed pulvinar proin gravida hendrerit lectus. Non odio euismod lacinia at quis risus sed vulputate odio. Nunc sed blandit libero volutpat sed cras ornare arcu. Adipiscing enim eu turpis egestas pretium aenean pharetra magna. Ut tristique et egestas quis ipsum suspendisse. Blandit cursus risus at ultrices mi tempus imperdiet nulla malesuada. -' + INDEX(event_time) ); \ No newline at end of file diff --git a/www/src/db_handler/analytics_interface.php b/www/src/db_handler/analytics_interface.php index de629c9..6ec9f67 100644 --- a/www/src/db_handler/analytics_interface.php +++ b/www/src/db_handler/analytics_interface.php @@ -1,16 +1,20 @@ \ No newline at end of file diff --git a/www/src/db_handler/mysql_analytics_handler.php b/www/src/db_handler/mysql_analytics_handler.php new file mode 100644 index 0000000..d6b1eb1 --- /dev/null +++ b/www/src/db_handler/mysql_analytics_handler.php @@ -0,0 +1,334 @@ +sql_connection = $sql_connection; + $this->hostname = $hostname; + } + + private function _exec($qery, $argtypes = '', ...$args) { + $stmt = $this->sql_connection->prepare($qery); + + if($argtypes != ""){ + $stmt->bind_param($argtypes, ...$args); + } + $stmt->execute(); + + return $stmt->get_result(); + } + + public function get_current_timestamp() { + return (int)($this->_exec( + "SELECT unix_timestamp(NOW()) AS ctime" + )->fetch_assoc()['ctime']); + } + + public function increment_counter($tags, $counter, $value = 1, $timestamp = null) { + $timestamp ??= $this->get_current_timestamp(); + + $qry = + "INSERT INTO analytics_summations + ( + time_bucket, + metric, + tags, + metric_value + ) + VALUES + ( + from_unixtime(floor(? / 300) * 300), + ?, + ?, + ? + ) AS new + ON DUPLICATE KEY + UPDATE metric_value=analytics_summations.metric_value + new.metric_value; + "; + + $this->_exec($qry, + "dssd", + $timestamp, $counter, json_encode($tags), $value); + } + + public function insert_event($event_tags, $event_text) { + $qry = + "INSERT INTO analytics_events ( + event_time, tags, event_text + ) + VALUES (NOW(), ?, ?)"; + + $this->_exec($qry, "ss", + json_encode($event_tags), $event_text); + } + + public function log_path_access( + $path, + $agent, + $referrer, + $time) { + + if(strlen($path) == 0) { + $path = '/'; + } + + $this->increment_counter([ + 'host' => $this->hostname, + 'path' => $path, + 'agent' => $agent, + 'referrer' => $referrer, + ], 'access_sum'); + + $this->increment_counter([ + 'host' => $this->hostname, + 'path' => $path + ], 'runtime', $time); + } + + public function log_path_errcode( + $path, $code, $message) { + + $this->insert_event([ + 'host' => $this->hostname, + 'path' => $path, + 'code' => $code + ], $message); + } + + public function generate_lp_line($table, $tags, $values, $timestamp) { + $out_str = $table; + + $line_tags = []; + foreach($tags AS $tag_key => $tag_value) { + if(!preg_match('/^[\w_]+$/', $tag_key)) { + throw new Exception('Invalid line tag key (' . $tag_key . ')!'); + } + + $tag_value = preg_replace('/([,=\s])/', '\\\\$0', $tag_value); + + $line_tags []= $tag_key . '=' . $tag_value; + } + + $line_values = []; + foreach($values AS $tag_key => $tag_value) { + if(!preg_match('/^[\w_]+$/', $tag_key)) { + throw new Exception('Invalid line value key (' . $tag_key . ')!'); + } + + if(gettype($tag_value) == 'string') { + $tag_value = preg_replace('/(["\])/', '\\\\$0', $tag_value); + $tag_value = preg_replace('/\n/', '\\\\n', $tag_value); + $tag_value = '"' . $tag_value . '"'; + } + elseif (gettype($tag_value) == 'integer') { + $tag_value = $tag_value . 'i'; + } + + $line_values []= $tag_key . '=' . $tag_value; + } + + return $table + . ',' . implode(',', $line_tags) + . ' ' . implode(',', $line_values) + . ' ' . $timestamp; + } + + public function pop_analytics($delete = true) { + $this->sql_connection->begin_transaction(); + + try { + $barrier_time = $this->_exec("SELECT NOW() - INTERVAL 6 MINUTE AS ctime")->fetch_assoc()['ctime']; + + $result = $this->_exec(" + SELECT * + FROM analytics_summations + WHERE time_bucket < ? + ORDER BY metric, time_bucket DESC", "s", $barrier_time); + + $data_category = "access_metrics"; + + $row = $result->fetch_assoc(); + $out_str = ''; + + while(isset($row)) { + $row_tags = json_decode($row['tags']); + $row_value = $row['metric_value']; + + $row_metric = $row['metric']; + + $out_str .= $this->generate_lp_line($data_category, $row_tags, [ + $row_metric => $row_value + ], strtotime($row['time_bucket']) . "000000000") . "\n"; + + $row = $result->fetch_assoc(); + } + + $result = $this->_exec(" + SELECT * + FROM analytics_events + WHERE event_time < ? + ORDER BY event_time DESC", "s", $barrier_time); + + while(isset($row)) { + $row_tags = json_decode($row['tags']); + $row_value = $row['event_text']; + + $row_metric = $row['metric']; + + $out_str .= $this->generate_lp_line($data_category, $row_tags, [ + $row_metric => $row_value + ], strtotime($row['time_bucket']) . "000000000") . "\n"; + + $row = $result->fetch_assoc(); + } + + if($delete) { + $this->_exec("DELETE FROM analytics_summations WHERE time_bucket <= ?", "s", $barrier_time); + } + + $this->sql_connection->commit(); + + return $out_str; + + } catch (\Throwable $th) { + $this->sql_connection->rollback(); + throw $th; + } + } + + public function pop_analytics_json($delete = true) { + $this->sql_connection->begin_transaction(); + + try { + $barrier_time = $this->_exec("SELECT NOW() - INTERVAL 6 MINUTE AS ctime")->fetch_assoc()['ctime']; + + $out_data = []; + + $result = $this->_exec(" + SELECT * + FROM analytics_summations + WHERE time_bucket < ? + ORDER BY metric, time_bucket DESC", "s", $barrier_time); + + $row = $result->fetch_assoc(); + + $current_metric_collection = []; + $current_time_bucket_collection = []; + + $current_metric = $row['metric'] ?? null; + $current_time_bucket = $row['time_bucket'] ?? null; + + while(isset($row)) { + $current_time_bucket_collection[]= [ + 'tags' => json_decode($row['tags']), + 'value' => floatval($row['metric_value']) + ]; + + $row = $result->fetch_assoc(); + + if(!isset($row) + OR ($row['time_bucket'] != $current_time_bucket) + OR ($row['metric'] != $current_metric)) { + + $current_metric_collection []= [ + 'time' => $current_time_bucket, + 'data' => $current_time_bucket_collection + ]; + + $current_time_bucket_collection = []; + $current_time_bucket = $row['time_bucket'] ?? null; + } + if(!isset($row) OR ($row['metric'] != $current_metric)) { + $out_data []= [ + 'metric' => $current_metric, + 'data' => $current_metric_collection + ]; + $current_metric_collection = []; + $current_metric = $row['metric'] ?? null; + } + } + + if($delete) { + $this->_exec("DELETE FROM analytics_summations WHERE time_bucket <= ?", "s", $barrier_time); + } + + $this->sql_connection->commit(); + + return json_encode($out_data); + + } catch (\Throwable $th) { + $this->sql_connection->rollback(); + throw $th; + } + } + + public function pop_analytics_old($delete = true) { + $this->sql_connection->begin_transaction(); + $out_data = ""; + + try { + $barrier_time = $this->_exec("SELECT NOW() - INTERVAL 6 MINUTE AS ctime")->fetch_assoc()['ctime']; + + $data = $this->_exec(" + SELECT * + FROM analytics_access_sums + WHERE time_bucket < ? + ", "s", $barrier_time)->fetch_all(MYSQLI_ASSOC); + + $data_prefix="analytics_access_sums"; + + foreach($data AS $post_data) { + $path = $post_data['request_path']; + if($path == '') { + $path = '/'; + } + $out_data .= $data_prefix . ",host=" . $post_data['host'] . ",agent=".$post_data['agent']; + $out_data .= ",path=".$path.",referrer=".$post_data['referrer']; + + $out_data .= " access_sum=" . $post_data['access_sum']; + $out_data .= " " . strtotime($post_data['time_bucket']) . "000000000\n"; + } + + $data = $this->_exec(" + SELECT * + FROM analytics_processing_time_sums + WHERE time_bucket < ? + ", "s", $barrier_time)->fetch_all(MYSQLI_ASSOC); + + $data_prefix="analytics_processing_time_sums"; + + foreach($data AS $post_data) { + $path = $post_data['request_path']; + if($path == '') { + $path = '/'; + } + $out_data .= $data_prefix . ",host=" . $post_data['host']; + $out_data .= ",path=".$path; + + $out_data .= " time_sum=" . $post_data['time_sum']; + $out_data .= " " . strtotime($post_data['time_bucket']) . "000000000\n"; + } + + if($delete) { + $this->_exec("DELETE FROM analytics_access_sums WHERE time_bucket <= ?", "s", $barrier_time); + $this->_exec("DELETE FROM analytics_processing_time_sums WHERE time_bucket <= ?", "s", $barrier_time); + } + + $this->sql_connection->commit(); + return $out_data; + + } catch (\Throwable $th) { + $this->sql_connection->rollback(); + + throw $th; + } + } +} + +?> \ No newline at end of file diff --git a/www/src/router.php b/www/src/router.php index 6db72cc..b3e62b4 100644 --- a/www/src/router.php +++ b/www/src/router.php @@ -19,6 +19,8 @@ parse_str($REQUEST_URI['query'] ?? '', $REQUEST_QUERY); require_once 'setup/permissions.php'; +require_once 'setup/analytics.php'; + if(preg_match('/^\/api/', $REQUEST_PATH)) { require_once 'serve/api.php'; } diff --git a/www/src/serve/api.php b/www/src/serve/api.php index 094dbc5..67b297c 100644 --- a/www/src/serve/api.php +++ b/www/src/serve/api.php @@ -23,6 +23,20 @@ switch($API_FUNCTION) { echo json_encode($sql_adapter->get_postdata($match[2])); break; + case 'metrics': + // TODO Change this to a "can access metrics", but whatever :> + if(!access_can_upload()) { + http_response_code(401); + echo json_encode([ + 'status' => '401 Unauthorized' + ]); + + die(); + } + + echo $analytics_adapter->pop_analytics($delete = true); + break; + case 'upload': if(!access_can_upload()) { http_response_code(401); @@ -81,8 +95,6 @@ switch($API_FUNCTION) { } $post_data['tags'] ??= []; - $post_data['tags'] []= "type:" . ($post_data['type'] ?? Post::deduce_type($file_path)); - $post_data['tags'] []= "path:" . $file_path; $sql_adapter->set_postdata($post_data); } diff --git a/www/src/setup/analytics.php b/www/src/setup/analytics.php new file mode 100644 index 0000000..e045c68 --- /dev/null +++ b/www/src/setup/analytics.php @@ -0,0 +1,66 @@ +log_path_access($REQUEST_PATH, + deduce_user_agent(), + $referrer, + $compute_time); + + if($analytics_enable_tail) { + echo ""; + } + + if(isset($analytics_post)) { + $analytics_post->increment_counter("compute_time", $compute_time); + + if(analytics_is_user()) { + $analytics_post->increment_counter("views"); + } + } +}); + +?> \ No newline at end of file diff --git a/www/src/setup/db.php b/www/src/setup/db.php index 39c340f..c4757bb 100644 --- a/www/src/setup/db.php +++ b/www/src/setup/db.php @@ -1,6 +1,7 @@ Date: Thu, 27 Mar 2025 23:33:49 +0100 Subject: [PATCH 6/6] fix(search): :adhesive_bandage: fix blog post listing --- www/src/setup/derg_insert.php | 3 +++ www/templates/fragments/blog/listing.html | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/www/src/setup/derg_insert.php b/www/src/setup/derg_insert.php index 8051a3e..b70f821 100644 --- a/www/src/setup/derg_insert.php +++ b/www/src/setup/derg_insert.php @@ -35,6 +35,9 @@ class DergInsertRenderer { if(isset($renderConfig['page'])) { $args['page'] = $this->postAdapter->get_post($renderConfig['page']); } + if(isset($renderConfig['search'])) { + $args['search_results'] = $this->postAdapter->search_posts($renderConfig['search']); + } return $this->twig->render($template, $args); } diff --git a/www/templates/fragments/blog/listing.html b/www/templates/fragments/blog/listing.html index e69de29..003848f 100644 --- a/www/templates/fragments/blog/listing.html +++ b/www/templates/fragments/blog/listing.html @@ -0,0 +1,4 @@ + +{% for post in search_results %} + {{ include('fragments/blog/card.html') }} +{% endfor %} \ No newline at end of file