Documents and Documentation (beta 1) #23
|
@ -1,14 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "bit-badger/feed-reader-central",
|
"name": "bit-badger/feed-reader-central",
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
"repositories": [
|
|
||||||
{
|
|
||||||
"type": "vcs",
|
|
||||||
"url": "https://git.bitbadger.solutions/bit-badger/pdo-document"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
"require": {
|
||||||
"bit-badger/pdo-document": "dev-develop",
|
"bit-badger/pdo-document": "^1",
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
"ext-dom": "*",
|
"ext-dom": "*",
|
||||||
"ext-pdo": "*",
|
"ext-pdo": "*",
|
||||||
|
|
46
src/composer.lock
generated
46
src/composer.lock
generated
|
@ -4,19 +4,20 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "e76207a80fdea0c9dc753a55e6f66bf0",
|
"content-hash": "f074a197e429ac24507becc14e0d99c3",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "bit-badger/pdo-document",
|
"name": "bit-badger/pdo-document",
|
||||||
"version": "dev-develop",
|
"version": "v1.0.0-alpha1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.bitbadger.solutions/bit-badger/pdo-document",
|
"url": "https://git.bitbadger.solutions/bit-badger/pdo-document",
|
||||||
"reference": "a10ecbb1cdfdf6dd8ab3c1884b09d9ba987b7ff5"
|
"reference": "f784f3e52cc1e4691fa347eefc82a2e4587c7f38"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-pdo": "*",
|
"ext-pdo": "*",
|
||||||
"netresearch/jsonmapper": "^4"
|
"netresearch/jsonmapper": "^4",
|
||||||
|
"php": ">=8.3"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^11"
|
"phpunit/phpunit": "^11"
|
||||||
|
@ -25,18 +26,35 @@
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"BitBadger\\PDODocument\\": "./src",
|
"BitBadger\\PDODocument\\": "./src",
|
||||||
"BitBadger\\PDODocument\\Mapper\\": "./src/Mapper",
|
"BitBadger\\PDODocument\\Query\\": "./src/Query",
|
||||||
"BitBadger\\PDODocument\\Query\\": "./src/Query"
|
"BitBadger\\PDODocument\\Mapper\\": "./src/Mapper"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
"psr-4": {
|
"license": [
|
||||||
"Test\\Unit\\": "./tests/unit",
|
"MIT"
|
||||||
"Test\\Integration\\": "./tests/integration",
|
],
|
||||||
"Test\\Integration\\SQLite\\": "./tests/integration/sqlite"
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Daniel J. Summers",
|
||||||
|
"email": "daniel@bitbadger.solutions",
|
||||||
|
"homepage": "https://bitbadger.solutions",
|
||||||
|
"role": "Developer"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"description": "Treat SQLite (and soon PostgreSQL) as a document store",
|
||||||
|
"keywords": [
|
||||||
|
"database",
|
||||||
|
"document",
|
||||||
|
"pdo",
|
||||||
|
"sqlite"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"email": "daniel@bitbadger.solutions",
|
||||||
|
"rss": "https://git.bitbadger.solutions/bit-badger/pdo-document.rss",
|
||||||
|
"source": "https://git.bitbadger.solutions/bit-badger/pdo-document"
|
||||||
},
|
},
|
||||||
"time": "2024-06-08T16:57:13+00:00"
|
"time": "2024-06-08T23:58:45+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "netresearch/jsonmapper",
|
"name": "netresearch/jsonmapper",
|
||||||
|
@ -93,9 +111,7 @@
|
||||||
"packages-dev": [],
|
"packages-dev": [],
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
"stability-flags": {
|
"stability-flags": [],
|
||||||
"bit-badger/pdo-document": 20
|
|
||||||
},
|
|
||||||
"prefer-stable": false,
|
"prefer-stable": false,
|
||||||
"prefer-lowest": false,
|
"prefer-lowest": false,
|
||||||
"platform": {
|
"platform": {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
namespace FeedReaderCentral;
|
namespace FeedReaderCentral;
|
||||||
|
|
||||||
use BitBadger\PDODocument\{
|
use BitBadger\PDODocument\{
|
||||||
Configuration, Custom, Document, DocumentException, DocumentList, Exists, Field, Find, Parameters, Patch, Query
|
Configuration, Custom, DocumentException, DocumentList, Exists, Field, Find, Parameters, Patch, Query
|
||||||
};
|
};
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
|
|
||||||
|
@ -50,14 +50,12 @@ class Feed
|
||||||
*/
|
*/
|
||||||
public static function fromParsed(ParsedFeed $parsed): static
|
public static function fromParsed(ParsedFeed $parsed): static
|
||||||
{
|
{
|
||||||
$it = new static();
|
return new static(
|
||||||
$it->user_id = $_SESSION[Key::USER_ID];
|
user_id: $_SESSION[Key::USER_ID],
|
||||||
$it->url = $parsed->url;
|
url: $parsed->url,
|
||||||
$it->title = $parsed->title;
|
title: $parsed->title,
|
||||||
$it->updated_on = $parsed->updatedOn;
|
updated_on: $parsed->updatedOn,
|
||||||
$it->checked_on = Data::formatDate('now');
|
checked_on: Data::formatDate('now'));
|
||||||
|
|
||||||
return $it;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,10 +76,10 @@ class Feed
|
||||||
if ($existing) {
|
if ($existing) {
|
||||||
if ($existing->published_on != $item->publishedOn
|
if ($existing->published_on != $item->publishedOn
|
||||||
|| ($existing->updated_on != ($item->updatedOn ?? ''))) {
|
|| ($existing->updated_on != ($item->updatedOn ?? ''))) {
|
||||||
Patch::byId(Table::ITEM, $existing->id, $item->patchFields());
|
Item::update($existing->id, $item);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Document::insert(Table::ITEM, Item::fromFeedItem($feedId, $item));
|
Item::add($feedId, $item);
|
||||||
}
|
}
|
||||||
return ['ok' => true];
|
return ['ok' => true];
|
||||||
} catch (DocumentException $ex) {
|
} catch (DocumentException $ex) {
|
||||||
|
@ -113,7 +111,7 @@ class Feed
|
||||||
$sql .= ' AND ' . Query::whereByFields([$readField]);
|
$sql .= ' AND ' . Query::whereByFields([$readField]);
|
||||||
} elseif (PURGE_TYPE == self::PURGE_BY_DAYS) {
|
} elseif (PURGE_TYPE == self::PURGE_BY_DAYS) {
|
||||||
$fields[] = Field::EQ('', Data::formatDate('-' . PURGE_NUMBER . ' day'), ':oldest');
|
$fields[] = Field::EQ('', Data::formatDate('-' . PURGE_NUMBER . ' day'), ':oldest');
|
||||||
$sql .= " AND date(coalesce(data->>'updated_on', data->>'published_on)) < date(:oldest)";
|
$sql .= " AND date(coalesce(data->>'updated_on', data->>'published_on')) < date(:oldest)";
|
||||||
} elseif (PURGE_TYPE == self::PURGE_BY_COUNT) {
|
} elseif (PURGE_TYPE == self::PURGE_BY_COUNT) {
|
||||||
$fields[] = Field::EQ('', PURGE_NUMBER, ':keep');
|
$fields[] = Field::EQ('', PURGE_NUMBER, ':keep');
|
||||||
$id = Configuration::$idField;
|
$id = Configuration::$idField;
|
||||||
|
@ -189,7 +187,10 @@ class Feed
|
||||||
return ['error' => "Already subscribed to feed $feed->url"];
|
return ['error' => "Already subscribed to feed $feed->url"];
|
||||||
}
|
}
|
||||||
|
|
||||||
Document::insert(Table::FEED, self::fromParsed($feed));
|
Custom::nonQuery(<<<'SQL'
|
||||||
|
INSERT INTO feed (data)
|
||||||
|
VALUES (json_set(:data, '$.id', (SELECT coalesce(max(data->>'id'), 0) + 1 FROM feed)))
|
||||||
|
SQL, Parameters::json(':data', self::fromParsed($feed)));
|
||||||
|
|
||||||
$doc = Find::firstByFields(Table::FEED, $fields, static::class);
|
$doc = Find::firstByFields(Table::FEED, $fields, static::class);
|
||||||
if (!$doc) return ['error' => 'Could not retrieve inserted feed'];
|
if (!$doc) return ['error' => 'Could not retrieve inserted feed'];
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
namespace FeedReaderCentral;
|
namespace FeedReaderCentral;
|
||||||
|
|
||||||
|
use BitBadger\PDODocument\{Custom, DocumentException, Parameters, Patch};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An item from a feed
|
* An item from a feed
|
||||||
*/
|
*/
|
||||||
|
@ -47,21 +49,42 @@ class Item
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an item document from a parsed feed item
|
* Add an item
|
||||||
*
|
*
|
||||||
* @param int $feedId The ID of the feed to which this item belongs
|
* @param int $feedId The ID of the feed to which the item belongs
|
||||||
* @param ParsedItem $item The parsed feed item
|
* @param ParsedItem $parsed The parsed item from the feed XML
|
||||||
* @return static The item document
|
* @throws DocumentException If any is encountered
|
||||||
*/
|
*/
|
||||||
public static function fromFeedItem(int $feedId, ParsedItem $item): static
|
public static function add(int $feedId, ParsedItem $parsed): void
|
||||||
{
|
{
|
||||||
return new static(
|
Custom::nonQuery(<<<'SQL'
|
||||||
feed_id: $feedId,
|
INSERT INTO item (data)
|
||||||
title: $item->title,
|
VALUES (json_set(:data, '$.id', (SELECT coalesce(max(data->>'id'), 0) + 1 FROM item)))
|
||||||
item_guid: $item->guid,
|
SQL, Parameters::json(':data', new static(
|
||||||
item_link: $item->link,
|
feed_id: $feedId,
|
||||||
published_on: $item->publishedOn,
|
title: $parsed->title,
|
||||||
updated_on: $item->updatedOn,
|
item_guid: $parsed->guid,
|
||||||
content: $item->content);
|
item_link: $parsed->link,
|
||||||
|
published_on: $parsed->publishedOn,
|
||||||
|
updated_on: $parsed->updatedOn,
|
||||||
|
content: $parsed->content)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an item
|
||||||
|
*
|
||||||
|
* @param int $id The ID of the item to be updated
|
||||||
|
* @param ParsedItem $parsed The parsed item from the feed XML
|
||||||
|
* @throws DocumentException If any is encountered
|
||||||
|
*/
|
||||||
|
public static function update(int $id, ParsedItem $parsed): void
|
||||||
|
{
|
||||||
|
Patch::byId(Table::ITEM, $id, [
|
||||||
|
'title' => $parsed->title,
|
||||||
|
'published_on' => $parsed->publishedOn,
|
||||||
|
'updated_on' => $parsed->updatedOn,
|
||||||
|
'content' => $parsed->content,
|
||||||
|
'is_read' => 0
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,39 +9,19 @@ use DOMNode;
|
||||||
*/
|
*/
|
||||||
class ParsedItem
|
class ParsedItem
|
||||||
{
|
{
|
||||||
/** @var string The title of the feed item */
|
|
||||||
public string $title = '';
|
|
||||||
|
|
||||||
/** @var string The unique ID for the feed item */
|
|
||||||
public string $guid = '';
|
|
||||||
|
|
||||||
/** @var string The link to the original content */
|
|
||||||
public string $link = '';
|
|
||||||
|
|
||||||
/** @var string When this item was published */
|
|
||||||
public string $publishedOn = '';
|
|
||||||
|
|
||||||
/** @var ?string When this item was last updated */
|
|
||||||
public ?string $updatedOn = null;
|
|
||||||
|
|
||||||
/** @var string The content for the item */
|
|
||||||
public string $content = '';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the fields needed to update the item in the database
|
* Constructor
|
||||||
*
|
*
|
||||||
* @return array The fields needed tu update an item
|
* @param string $guid The unique ID for the feed item
|
||||||
|
* @param string $title The title of the feed item
|
||||||
|
* @param string $link The link to the original content
|
||||||
|
* @param string $publishedOn When this item was published
|
||||||
|
* @param string|null $updatedOn When this item was last updated
|
||||||
|
* @param string $content The content for the item
|
||||||
*/
|
*/
|
||||||
public function patchFields(): array
|
private function __construct(public string $guid = '', public string $title = '', public string $link = '',
|
||||||
{
|
public string $publishedOn = '', public ?string $updatedOn = null,
|
||||||
return [
|
public string $content = '') { }
|
||||||
'title' => $this->title,
|
|
||||||
'published_on' => $this->publishedOn,
|
|
||||||
'updated_on' => $this->updatedOn,
|
|
||||||
'content' => $this->content,
|
|
||||||
'is_read' => 0
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a feed item from an Atom feed's `<entry>` tag
|
* Construct a feed item from an Atom feed's `<entry>` tag
|
||||||
|
@ -64,15 +44,13 @@ class ParsedItem
|
||||||
}
|
}
|
||||||
if ($link == '' && str_starts_with($guid, 'http')) $link = $guid;
|
if ($link == '' && str_starts_with($guid, 'http')) $link = $guid;
|
||||||
|
|
||||||
$item = new static();
|
return new static(
|
||||||
$item->guid = $guid;
|
guid: $guid,
|
||||||
$item->title = ParsedFeed::atomValue($node, 'title');
|
title: ParsedFeed::atomValue($node, 'title'),
|
||||||
$item->link = $link;
|
link: $link,
|
||||||
$item->publishedOn = Data::formatDate(ParsedFeed::atomValue($node, 'published'));
|
publishedOn: Data::formatDate(ParsedFeed::atomValue($node, 'published')),
|
||||||
$item->updatedOn = Data::formatDate(ParsedFeed::atomValue($node, 'updated'));
|
updatedOn: Data::formatDate(ParsedFeed::atomValue($node, 'updated')),
|
||||||
$item->content = ParsedFeed::atomValue($node, 'content');
|
content: ParsedFeed::atomValue($node, 'content'));
|
||||||
|
|
||||||
return $item;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -87,16 +65,14 @@ class ParsedItem
|
||||||
$updNodes = $node->getElementsByTagNameNS(ParsedFeed::ATOM_NS, 'updated');
|
$updNodes = $node->getElementsByTagNameNS(ParsedFeed::ATOM_NS, 'updated');
|
||||||
$encNodes = $node->getElementsByTagNameNS(ParsedFeed::CONTENT_NS, 'encoded');
|
$encNodes = $node->getElementsByTagNameNS(ParsedFeed::CONTENT_NS, 'encoded');
|
||||||
|
|
||||||
$item = new static();
|
return new static(
|
||||||
$item->guid = $itemGuid == 'guid not found' ? ParsedFeed::rssValue($node, 'link') : $itemGuid;
|
guid: $itemGuid == 'guid not found' ? ParsedFeed::rssValue($node, 'link') : $itemGuid,
|
||||||
$item->title = ParsedFeed::rssValue($node, 'title');
|
title: ParsedFeed::rssValue($node, 'title'),
|
||||||
$item->link = ParsedFeed::rssValue($node, 'link');
|
link: ParsedFeed::rssValue($node, 'link'),
|
||||||
$item->publishedOn = Data::formatDate(ParsedFeed::rssValue($node, 'pubDate'));
|
publishedOn: Data::formatDate(ParsedFeed::rssValue($node, 'pubDate')),
|
||||||
$item->updatedOn = Data::formatDate($updNodes->length > 0 ? $updNodes->item(0)->textContent : null);
|
updatedOn: Data::formatDate($updNodes->length > 0 ? $updNodes->item(0)->textContent : null),
|
||||||
$item->content = $encNodes->length > 0
|
content: $encNodes->length > 0
|
||||||
? $encNodes->item(0)->textContent
|
? $encNodes->item(0)->textContent
|
||||||
: ParsedFeed::rssValue($node, 'description');
|
: ParsedFeed::rssValue($node, 'description'));
|
||||||
|
|
||||||
return $item;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace FeedReaderCentral;
|
namespace FeedReaderCentral;
|
||||||
|
|
||||||
use BitBadger\PDODocument\{Custom, Document, DocumentException, Field, Find, Parameters, Query};
|
use BitBadger\PDODocument\{Custom, DocumentException, Field, Find, Parameters, Query};
|
||||||
use BitBadger\PDODocument\Mapper\ExistsMapper;
|
use BitBadger\PDODocument\Mapper\ExistsMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,7 +40,10 @@ class User
|
||||||
*/
|
*/
|
||||||
public static function add(string $email, string $password): void
|
public static function add(string $email, string $password): void
|
||||||
{
|
{
|
||||||
Document::insert(Table::USER, new User(email: $email, password: $password));
|
Custom::nonQuery(<<<'SQL'
|
||||||
|
INSERT INTO user (data)
|
||||||
|
VALUES (json_set(:data, '$.id', (SELECT coalesce(max(data->>'id'), 0) + 1 FROM user)))
|
||||||
|
SQL, Parameters::json(':data', new User(email: $email, password: $password)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -29,7 +29,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'DELETE') {
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
try {
|
try {
|
||||||
$isNew = $_POST['id'] == 'new';
|
$isNew = $_POST['id'] == '-1';
|
||||||
if ($isNew) {
|
if ($isNew) {
|
||||||
$result = Feed::add($_POST['url']);
|
$result = Feed::add($_POST['url']);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -19,7 +19,7 @@ $feeds = Custom::list(Query\Find::byFields(Table::FEED, [$field]) . " ORDER BY l
|
||||||
$field->appendParameter([]), new DocumentMapper(Feed::class));
|
$field->appendParameter([]), new DocumentMapper(Feed::class));
|
||||||
|
|
||||||
page_head('Your Feeds');
|
page_head('Your Feeds');
|
||||||
echo '<h1>Your Feeds</h1><article><p class=action_buttons>' . hx_get('/feed/?id=new', 'Add Feed') . '</p>';
|
echo '<h1>Your Feeds</h1><article><p class=action_buttons>' . hx_get('/feed/?id=-1', 'Add Feed') . '</p>';
|
||||||
foreach ($feeds->items() as /** @var Feed $feed */ $feed) {
|
foreach ($feeds->items() as /** @var Feed $feed */ $feed) {
|
||||||
$item = Table::ITEM;
|
$item = Table::ITEM;
|
||||||
$counts = Custom::single(<<<SQL
|
$counts = Custom::single(<<<SQL
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
use BitBadger\PDODocument\Configuration;
|
||||||
use FeedReaderCentral\{Key, Security, User};
|
use FeedReaderCentral\{Key, Security, User};
|
||||||
|
|
||||||
require 'app-config.php';
|
require 'app-config.php';
|
||||||
|
@ -134,6 +135,7 @@ function frc_redirect(string $value): never
|
||||||
}
|
}
|
||||||
session_commit();
|
session_commit();
|
||||||
header("Location: $value", true, 303);
|
header("Location: $value", true, 303);
|
||||||
|
Configuration::resetPDO();
|
||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user