feat(yaps): first lifesigns of the yapping system
Some checks reported warnings
/ phplint (push) Has been cancelled

This commit is contained in:
David Bailey 2025-04-28 10:46:56 +02:00
parent 143c932c88
commit f8af3c5c59
7 changed files with 321 additions and 17 deletions

View file

@ -55,32 +55,33 @@ CREATE TABLE dev_post_markdown (
FULLTEXT(post_markdown)
);
CREATE TABLE dev_feeds (
post_id INTEGER NOT NULL,
feed_key VARCHAR(32),
feed_id VARCHAR(40) NOT NULL,
CREATE TABLE dev_yaps (
yap_id INTEGER AUTO_INCREMENT,
PRIMARY KEY(yap_id),
feed_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
post_path VARCHAR(255) NOT NULL,
yap_category VARCHAR(32) NOT NULL,
yap_tag VARCHAR(40) NOT NULL,
feed_metadata JSON DEFAULT NULL,
-- Uniqueness detection based on associated path, category and tag
yap_hash CHAR(32) AS (MD5(CONCAT(post_path, yap_category, yap_tag))),
CONSTRAINT YAPS_UNIQUE UNIQUE(yap_hash),
feed_text TEXT,
yap_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
yap_metadata JSON DEFAULT NULL,
-- Primary key as true ID to allow for deterministic saving/recreating
CONSTRAINT PK_FEED PRIMARY KEY(post_id, feed_key, feed_id),
FOREIGN KEY(post_id) REFERENCES dev_posts(post_id)
ON DELETE CASCADE,
yap_text TEXT,
-- Make it possible to look up changes from e.g. a commit hash inexpensively
INDEX(feed_id),
INDEX(yap_tag),
-- Make it possible to look up specific post feeds efficiently (e.g. changelog)
INDEX(post_id, feed_key, feed_created_at),
INDEX(post_path, yap_category, yap_created_at),
-- Make it possible to globally look up specific feeds efficiently
INDEX(feed_key, feed_created_at),
INDEX(yap_category, yap_created_at),
-- And just in general, make searching feeds in a timeframe efficient
INDEX(feed_created_at),
INDEX(yap_created_at),
FULLTEXT(feed_text)
FULLTEXT(yap_text)
);
CREATE TABLE analytics_summations (

View file

@ -0,0 +1,158 @@
<?php
require_once 'db_interface.php';
require_once 'yaps_interface.php';
class MySQL_YapsHandler
implements YapsInterface {
private $debugging;
private $sql_connection;
private $hostname;
private $db_prefix;
function __construct($sql_connection, $hostname, $db_prefix) {
$this->sql_connection = $sql_connection;
$this->hostname = $hostname;
$this->db_prefix = $db_prefix;
$this->debugging = false;
}
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 add_yap($yap_data_ext) {
# A little copy to prevent accidentally mutating
# external data
$yap_data = $yap_data_ext;
if(!isset($yap_data['tag'])) {
throw new Exception('Yap is missing a tag!');
}
if(!isset($yap_data['post'])) {
throw new Exception('Yap is missing a post!');
}
if(!isset($yap_data['category'])) {
throw new Exception('Yap is missing a category!');
}
var_dump($yap_data);
$yap_tag = $yap_data['tag'];
unset($yap_data['tag']);
$yap_post = $yap_data['post'];
unset($yap_data['post']);
$yap_post = sanitize_post_path($yap_post);
$yap_cateogry = $yap_data['category'];
unset($yap_data['category']);
$yap_text = $yap_data['text'] ?? null;
unset($yap_data['text']);
date_default_timezone_set('UTC');
$yap_created_at = $yap_data['created_at'] ?? null;
unset($yap_data['created_at']);
$yap_data = json_encode($yap_data);
$qry = "
INSERT INTO {$this->db_prefix}_yaps
(`post_path`, `yap_category`, `yap_tag`,
`yap_created_at`, `yap_metadata`, `yap_text` )
VALUES ( ?, ?, ?,
COALESCE(?, NOW()), ?, ? ) AS new
ON DUPLICATE KEY
UPDATE
{$this->db_prefix}_yaps.yap_metadata = new.yap_metadata,
{$this->db_prefix}_yaps.yap_text = new.yap_text;
";
$this->_exec($qry,
"ssssss",
$yap_post, $yap_cateogry, $yap_tag,
$yap_created_at, $yap_data, $yap_text);
}
public function get_yaplist($args = []) {
$category = $args['category'] ?? null;
$post = $args['post'] ?? $args['path'] ?? null;
$tag = $args['tag'] ?? null;
$before = $args['before'] ?? null;
$limit = $args['limit'] ?? null;
$qry_where = [];
$qry_where_data = [];
$qry_where_types = '';
if(isset($category)) {
if($category[-1] = '%') {
$qry_where []= 'yap_category LIKE ?';
}
else {
$qry_where []= 'yap_category = ?';
}
$qry_where_data []= $category;
$qry_where_types .= 's';
}
if(isset($post)) {
if($post[-1] = '%') {
$qry_where []= 'post_path LIKE ?';
}
else {
$qry_where []= 'post_path = ?';
}
$qry_where_data []= $post;
$qry_where_types .= 's';
}
if(isset($tag)) {
$qry_where []= 'yap_tag = ?';
$qry_where_data [] = $tag;
$qry_where_types .= 's';
}
if(isset($before)) {
$qry_where []= 'yap_created_at < ?';
$qry_where_data []= $before;
$qry_where_types .= 's';
}
$limit = min(max(intval($limit ?? 30), 0), 30);
$qry_where []= 'TRUE';
$qry = "
SELECT *
FROM {$this->db_prefix}_yaps
WHERE
" . implode(' AND ', $qry_where) .
" ORDER BY yap_created_at DESC
LIMIT ?";
$yaplist = $this->_exec($qry,
$qry_where_types . "i",
...array_merge($qry_where_data, [$limit]))->fetch_all(MYSQLI_ASSOC);
return $yaplist;
}
}
?>

View file

@ -0,0 +1,68 @@
<?php
interface YapsInterface {
/* Yaps format:
*
* The yaps are even simpler versions of Postdata,
* as they will have almost no defined metadata.
*
* The purpose of yaps is to provide very lightweight
* additions to existing posts.
*
* The only properly defined fields are the following:
* - Relevant Post ID
* - yap category ("changelog", "comment", "build log", etc.)
* - yap tag/hash (will e.g. use the Git Commit Hash for changelogs)
* - yap creation time
* - yap text
* - other yap data (free-form :D)
*
* There will be little to no post-processing by the
* database, as a lot is somewhat free-form.
* No settings, no caching, etc.
*
* Instead, searching yaps will be a fair bit more
* important, as they act as a lot of the feeds!
* RSS/Atom will run over "changelog" yaps, and changelogs
* will be displayed in most posts when present.
* The system might also serve user comments, as well as
* a posting alternative to social media feeds...
* As well as a few WebMentions comments ^^'
*
* Oh yes, Yaps will always be ordered by time, descending.
* It's feeds :P
* */
/* Adds a yap, or overwrites/updates an existing one
* The yap-data MUST include the following:
* - post (string path!)
* - tag
* - category
*
* And may include:
* - text (which is added to yap_text)
* - created_at (if not set, NOW() is used)
*
* With other fields slapped into yap_metadata
*/
public function add_yap($yap_data);
/* Returns a list of yaps for the given filters.
* - Category must always be set, but may be a "LIKE" wildcard
* - post *may* be set. If set, only yaps for that given post are returned
* - $since, which, if set and a valid timestamp, will return yaps after that time
* - $limit, which should be obvious
*
* This function always returns an array of yaps, chronologically ordered.
*
* The following search args are supported:
* - category: String, matched to the Yap Category. Can have a trailing % for LIKE search
* - post: String, post path to look for. Can have a trailing % for LIKE search
* - tag: Yap tag to look for.
* - before: Datetime string. Shows yaps before the given date
* - limit: Limit the number of returned yaps
*/
public function get_yaplist($args);
}
?>

View file

@ -36,6 +36,37 @@ switch($API_FUNCTION) {
echo $analytics_adapter->pop_analytics($delete = true);
break;
case 'db_yaps':
echo json_encode($yap_adapter->get_yaplist($_GET));
break;
case 'add_yap':
if( !isset($_POST['path'])
or !isset($_POST['yap_category'])
or !isset($_POST['yap_text'])) {
echo json_encode([
'status' => 'Missing paramters (must POST path, category and a text!)'
]);
die();
}
$yap_data = [
'post' => $_POST['path'],
'category' => $_POST['yap_category'],
'text' => $_POST['yap_text']
];
$yap_data['tag'] = MD5($_POST['path'] . microtime());
$yap_adapter->add_yap($yap_data);
echo json_encode([
'status' => '200 OK',
'yap' => $yap_data
]);
break;
case 'upload':
if(!access_can_upload()) {

View file

@ -54,10 +54,15 @@ function render_post($post, $args = []) {
$args);
}
if($REQUEST_PATH == '/upload') {
if($REQUEST_PATH == '/upload.php') {
render_root_template('upload.html');
die();
}
if($REQUEST_PATH == '/yap.php') {
render_root_template('_dev_add_yap.html');
die();
}
if($REQUEST_PATH == '/search/') {
$search_results = [];
$display_type = 'none';

View file

@ -2,6 +2,8 @@
require_once 'db_handler/mysql_handler.php';
require_once 'db_handler/mysql_analytics_handler.php';
require_once 'db_handler/mysql_yaps_handler.php';
require_once 'db_handler/post_handler.php';
$db_params = $SITE_CONFIG['db'];
@ -32,6 +34,9 @@ $sql_adapter = new MySQLHandler($db_connection,
$analytics_adapter = new MySQLAnalyticsHandler($db_connection,
$SITE_CONFIG['site_defaults']['uri_prefix']);
$yap_adapter = new MySQL_YapsHandler($db_connection,
$SITE_CONFIG['site_defaults']['uri_prefix'],
$db_params['prefix']);
$adapter = new PostHandler($sql_adapter);

View file

@ -0,0 +1,36 @@
{% extends "root.html" %}
{% block second_title %}
<h2> YAP SOMETHIN' </h2>
{% endblock %}
{%block main_content%}
<article>
<form method="post" enctype="multipart/form-data"
action="/api/add_yap"
hx-target="#yap-result"
hx-swap="innerHTML">
<label for="ACCESS_KEY"> Password: </label>
<input type="password" id="ACCESS_KEY" name="ACCESS_KEY"/>
<label for="path"> Path: </label>
<input type="text" id="path" name="path"/>
<label for="yap_category"> Yap Category: </label>
<input type="text" id="yap_category" name="yap_category"
value="yap"/>
<textarea type="file" id="yap_text" name="yap_text">
</textarea>
<button>Submit</button>
</form>
Return data:
<code id="yap-result">
</code>
</article>
{%endblock%}