From 94b65aec8cab5cf3b23315e6ec1f02d5c7ea92d9 Mon Sep 17 00:00:00 2001 From: David Bailey Date: Wed, 20 Dec 2023 18:52:28 +0100 Subject: [PATCH] feat: :sparkles: add post access usage metrics --- docker_dev/mysql_schema.sql | 14 +++ www/mysql_adapter.php | 41 ++++++++ www/router.php | 180 +++++++++++++++++++++--------------- 3 files changed, 161 insertions(+), 74 deletions(-) diff --git a/docker_dev/mysql_schema.sql b/docker_dev/mysql_schema.sql index 6801fcd..f2f4cde 100644 --- a/docker_dev/mysql_schema.sql +++ b/docker_dev/mysql_schema.sql @@ -26,6 +26,20 @@ CREATE TABLE posts ( INDEX(post_path_depth, post_path) ); +CREATE TABLE path_access_counts ( + post_path VARCHAR(255), + agent VARCHAR(255), + + path_last_access_time DATETIME NOT NULL + DEFAULT CURRENT_TIMESTAMP + ON UPDATE CURRENT_TIMESTAMP, + + path_access_count INTEGER DEFAULT 0, + path_processing_time DOUBLE PRECISION DEFAULT 0, + + PRIMARY KEY(post_path, agent), + INDEX(path_last_access_time) +); INSERT INTO posts (post_path, post_path_depth, post_metadata, post_content) VALUES ( diff --git a/www/mysql_adapter.php b/www/mysql_adapter.php index 324a767..72b0009 100644 --- a/www/mysql_adapter.php +++ b/www/mysql_adapter.php @@ -103,6 +103,47 @@ class MySQLAdapter { } } + function log_post_access($post_path, $agent, $time) { + $qry = " + INSERT INTO path_access_counts + (post_path, agent, + path_access_count, + path_processing_time) + VALUES ( ?, ?, 1, ? ) AS new + ON DUPLICATE KEY + UPDATE path_access_count=path_access_counts.path_access_count+1, + path_processing_time=path_access_counts.path_processing_time+new.path_processing_time; + "; + + $this->_exec($qry, "ssd", $post_path, $agent, $time); + } + + function get_post_access_counters() { + $qry = " + SELECT post_path, agent, path_access_count, path_processing_time + FROM path_access_counts + WHERE path_last_access_time > ( CURRENT_TIMESTAMP - INTERVAL 10 MINUTE ); + "; + + $data = $this->_exec($qry, "")->fetch_all(MYSQLI_ASSOC); + + $out_data = []; + + foreach($data AS $post_data) { + $path = $post_data['post_path']; + + $agent_data = ($out_data[$path] ?? []); + + $agent_data[$post_data['agent']] = [ + 'count' => $post_data['path_access_count'], + 'time' => round($post_data['path_processing_time'], 6) + ]; + + $out_data[$path] = $agent_data; + } + + return $out_data; + } function update_or_create_post($post_path, $post_metadata, $post_content) { $post_path = $this->_sanitize_path($post_path); $path_depth = substr_count($post_path, "/"); diff --git a/www/router.php b/www/router.php index bc62715..84b30d8 100644 --- a/www/router.php +++ b/www/router.php @@ -1,5 +1,7 @@ addRuntimeLoader(new class implements RuntimeLoaderInterface { } }); -$SURI = $_SERVER['REQUEST_URI']; +function deduce_user_agent() { + $real_agent=$_SERVER['HTTP_USER_AGENT']; -if(preg_match('/^\/api\/admin/', $SURI)) { - header('Content-Type: application/json'); - - $user_api_key = ''; - if(isset($_GET['api_key'])) { - $user_api_key = $_GET['api_key']; + if(preg_match('/(?:Mozilla|Chrome|Chromium)/', $real_agent)) { + return "web"; } - if(isset($_POST['api_key'])) { - $user_api_key = $_POST['api_key']; + elseif(preg_match('/(?:google)/', $real_agent)) { + return "bot/google"; } - - if($user_api_key != file_get_contents('secrets/api_admin_key')) { - http_response_code(401); - - echo json_encode([ - "authorized" => false - ]); - - die(); + else { + return "unidentified"; } +} - if($SURI = '/api/admin/upload') { - $adapter->handle_upload($_POST['post_path'], $_FILES['post_data']['tmp_name']); +$user_agent = deduce_user_agent(); - echo json_encode(["ok" => true]); - } -} elseif(preg_match('/^\/api/', $SURI)) { - if(preg_match('/^\/api\/posts(.*)$/', $SURI, $match)) { +function generate_website($SURI) { + global $twig; + global $adapter; + if(preg_match('/^\/api\/admin/', $SURI)) { header('Content-Type: application/json'); - echo json_encode($adapter->get_post_by_path($match[1])); - } elseif(preg_match('/^\/api\/subposts(.*)$/', $SURI, $match)) { + $user_api_key = ''; + if(isset($_GET['api_key'])) { + $user_api_key = $_GET['api_key']; + } + if(isset($_POST['api_key'])) { + $user_api_key = $_POST['api_key']; + } - header('Content-Type: application/json'); - echo json_encode(get_subposts($match[1])); - } elseif($SURI == '/api/upload') { + if($user_api_key != file_get_contents('secrets/api_admin_key')) { + http_response_code(401); - echo $twig->render('upload.html'); - } -} elseif(preg_match('/^\s*image/', $_SERVER['HTTP_ACCEPT'])) { - header('Location: /raw' . $SURI); - exit(0); -} elseif(true) { - $post = $adapter->get_post_by_path($SURI); + echo json_encode([ + "authorized" => false + ]); - if(!$post['found']) { - echo $twig->render('post_types/rrror.html',[ + die(); + } + + if($SURI = '/api/admin/upload') { + $adapter->handle_upload($_POST['post_path'], $_FILES['post_data']['tmp_name']); + + echo json_encode(["ok" => true]); + } + } elseif(preg_match('/^\/api/', $SURI)) { + if($SURI == '/api/post_counters') { + header('Content-Type: application/json'); + echo json_encode($adapter->get_post_access_counters()); + } elseif(preg_match('/^\/api\/posts(.*)$/', $SURI, $match)) { + + header('Content-Type: application/json'); + echo json_encode($adapter->get_post_by_path($match[1])); + + } elseif(preg_match('/^\/api\/subposts(.*)$/', $SURI, $match)) { + + header('Content-Type: application/json'); + echo json_encode(get_subposts($match[1])); + } elseif($SURI == '/api/upload') { + + echo $twig->render('upload.html'); + } + } elseif(preg_match('/^\s*image/', $_SERVER['HTTP_ACCEPT'])) { + header('Location: /raw' . $SURI); + exit(0); + } elseif(true) { + $post = $adapter->get_post_by_path($SURI); + + if(!$post['found']) { + echo $twig->render('post_types/rrror.html',[ + "error_code" => '404 Hoard not found!', + "error_description" => "Well, we searched + far and wide for `" . $SURI . "` but + somehow it must have gotten lost... Sorry!", + "post" => $post + ]); + + die(); + } + + if($post['post_metadata']['type'] == 'directory') { + if(preg_match('/^(.*[^\/])((?:#.*)?)$/', $SURI, $match)) { + header('Location: ' . $match[1] . '/' . $match[2]); + + return; + } + + echo $twig->render('post_types/directory.html', [ + "post" => $post, + "subposts" => $adapter->get_subposts_by_path($SURI) + ]); + } + elseif($post['post_metadata']['type'] == 'text/markdown') { + echo $twig->render('post_types/markdown.html', [ + "post" => $post + ]); + } + elseif($post['post_metadata']['type'] == 'image') { + echo $twig->render('post_types/image.html', [ + "post" => $post + ]); + } + + } else { + echo $twig->render('rrror.html',[ "error_code" => '404 Hoard not found!', "error_description" => "Well, we searched far and wide for `" . $SURI . "` but - somehow it must have gotten lost... Sorry!", - "post" => $post - ]); - - exit(0); + somehow it must have gotten lost... Sorry!" + ]); } - - if($post['post_metadata']['type'] == 'directory') { - if(preg_match('/^(.*[^\/])((?:#.*)?)$/', $SURI, $match)) { - header('Location: ' . $match[1] . '/' . $match[2]); - exit(0); - } - - echo $twig->render('post_types/directory.html', [ - "post" => $post, - "subposts" => $post['subposts'] - ]); - } - elseif($post['post_metadata']['type'] == 'text/markdown') { - echo $twig->render('post_types/markdown.html', [ - "post" => $post, - "subposts" => $post['subposts'] - ]); - } - elseif($post['post_metadata']['type'] == 'image') { - echo $twig->render('post_types/image.html', [ - "post" => $post - ]); - } - -} else { - echo $twig->render('rrror.html',[ - "error_code" => '404 Hoard not found!', - "error_description" => "Well, we searched - far and wide for `" . $SURI . "` but - somehow it must have gotten lost... Sorry!" - ]); } +generate_website($_SERVER['REQUEST_URI']); + +$data_time_end = microtime(true); + +$adapter->log_post_access($_SERVER['REQUEST_URI'], + $user_agent, + $data_time_end - $data_time_start) + ?>