Documents and Documentation (beta 1) #23

Merged
danieljsummers merged 16 commits from doc-experiment into main 2024-06-12 02:07:36 +00:00
18 changed files with 197 additions and 233 deletions
Showing only changes of commit 7231a95fca - Show all commits

View File

@ -2,7 +2,7 @@
/** The current Feed Reader Central version */ /** The current Feed Reader Central version */
use BitBadger\Documents\SQLite\Configuration; use BitBadger\PDODocument\Configuration;
use FeedReaderCentral\Data; use FeedReaderCentral\Data;
const FRC_VERSION = '1.0.0-beta1'; const FRC_VERSION = '1.0.0-beta1';
@ -26,7 +26,7 @@ function display_version(): string {
require __DIR__ . '/vendor/autoload.php'; require __DIR__ . '/vendor/autoload.php';
require 'user-config.php'; require 'user-config.php';
Configuration::useDbFileName(implode(DIRECTORY_SEPARATOR, [__DIR__, 'data', DATABASE_NAME])); Configuration::$pdoDSN = 'sqlite:' . implode(DIRECTORY_SEPARATOR, [__DIR__, 'data', DATABASE_NAME]);
Data::ensureDb(); Data::ensureDb();
/** @var string The date the world wide web was created */ /** @var string The date the world wide web was created */

View File

@ -4,19 +4,16 @@
"repositories": [ "repositories": [
{ {
"type": "vcs", "type": "vcs",
"url": "https://git.bitbadger.solutions/bit-badger/documents-common" "url": "https://git.bitbadger.solutions/bit-badger/pdo-document"
},
{
"type": "vcs",
"url": "https://git.bitbadger.solutions/bit-badger/documents-sqlite"
} }
], ],
"require": { "require": {
"bit-badger/documents-sqlite": "dev-conversion", "bit-badger/pdo-document": "dev-develop",
"ext-sqlite3": "*",
"ext-dom": "*",
"ext-curl": "*", "ext-curl": "*",
"ext-readline": "*" "ext-dom": "*",
"ext-pdo": "*",
"ext-readline": "*",
"ext-sqlite3": "*"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

51
src/composer.lock generated
View File

@ -4,17 +4,18 @@
"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": "029a3af4ce4e5cc5488c1ca634a8af61", "content-hash": "e76207a80fdea0c9dc753a55e6f66bf0",
"packages": [ "packages": [
{ {
"name": "bit-badger/documents-common", "name": "bit-badger/pdo-document",
"version": "dev-conversion", "version": "dev-develop",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://git.bitbadger.solutions/bit-badger/documents-common", "url": "https://git.bitbadger.solutions/bit-badger/pdo-document",
"reference": "9dd8059e80423c67c79c62ff27e92f920d5f041b" "reference": "afc5d8009507a297c8169be93aee1085e652eedc"
}, },
"require": { "require": {
"ext-pdo": "*",
"netresearch/jsonmapper": "^4" "netresearch/jsonmapper": "^4"
}, },
"require-dev": { "require-dev": {
@ -23,32 +24,9 @@
"type": "library", "type": "library",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"BitBadger\\Documents\\": "./", "BitBadger\\PDODocument\\": "./src",
"BitBadger\\Documents\\Query\\": "./Query" "BitBadger\\PDODocument\\Mapper\\": "./src/Mapper",
} "BitBadger\\PDODocument\\Query\\": "./src/Query"
},
"time": "2024-06-03T15:26:37+00:00"
},
{
"name": "bit-badger/documents-sqlite",
"version": "dev-conversion",
"source": {
"type": "git",
"url": "https://git.bitbadger.solutions/bit-badger/documents-sqlite",
"reference": "1439742c40ee8b8accd8d70f2ce94df458c6bc42"
},
"require": {
"bit-badger/documents-common": "dev-conversion",
"ext-sqlite3": "*"
},
"require-dev": {
"phpunit/phpunit": "^11"
},
"type": "library",
"autoload": {
"psr-4": {
"BitBadger\\Documents\\SQLite\\": "./src",
"BitBadger\\Documents\\SQLite\\Query\\": "./src/Query"
} }
}, },
"autoload-dev": { "autoload-dev": {
@ -57,7 +35,7 @@
"Test\\Integration\\": "./tests/integration" "Test\\Integration\\": "./tests/integration"
} }
}, },
"time": "2024-06-03T22:16:08+00:00" "time": "2024-06-04T12:10:57+00:00"
}, },
{ {
"name": "netresearch/jsonmapper", "name": "netresearch/jsonmapper",
@ -115,15 +93,16 @@
"aliases": [], "aliases": [],
"minimum-stability": "dev", "minimum-stability": "dev",
"stability-flags": { "stability-flags": {
"bit-badger/documents-sqlite": 20 "bit-badger/pdo-document": 20
}, },
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
"ext-sqlite3": "*",
"ext-dom": "*",
"ext-curl": "*", "ext-curl": "*",
"ext-readline": "*" "ext-dom": "*",
"ext-pdo": "*",
"ext-readline": "*",
"ext-sqlite3": "*"
}, },
"platform-dev": [], "platform-dev": [],
"plugin-api-version": "2.6.0" "plugin-api-version": "2.6.0"

View File

@ -2,16 +2,16 @@
namespace FeedReaderCentral; namespace FeedReaderCentral;
use BitBadger\Documents\DocumentException; use BitBadger\PDODocument\Configuration;
use BitBadger\Documents\Field; use BitBadger\PDODocument\Custom;
use BitBadger\Documents\SQLite\Configuration; use BitBadger\PDODocument\Definition;
use BitBadger\Documents\SQLite\Custom; use BitBadger\PDODocument\DocumentException;
use BitBadger\Documents\SQLite\Definition; use BitBadger\PDODocument\Field;
use BitBadger\Documents\StringMapper; use BitBadger\PDODocument\Mapper\StringMapper;
use DateTimeImmutable; use DateTimeImmutable;
use DateTimeInterface; use DateTimeInterface;
use Exception; use Exception;
use SQLite3; use PDO;
/** /**
* A centralized place for data access for the application * A centralized place for data access for the application
@ -21,18 +21,18 @@ class Data
/** /**
* Create the search index and synchronization triggers for the item table * Create the search index and synchronization triggers for the item table
* *
* @param SQLite3 $db The database connection on which these will be created * @param PDO $pdo The database connection on which these will be created
* @throws DocumentException If any is encountered * @throws DocumentException If any is encountered
*/ */
public static function createSearchIndex(SQLite3 $db): void public static function createSearchIndex(PDO $pdo): void
{ {
Custom::nonQuery("CREATE VIRTUAL TABLE item_search USING fts5(content, content='item', content_rowid='id')", Custom::nonQuery("CREATE VIRTUAL TABLE item_search USING fts5(content, content='item', content_rowid='id')",
[], $db); [], $pdo);
Custom::nonQuery(<<<'SQL' Custom::nonQuery(<<<'SQL'
CREATE TRIGGER item_ai AFTER INSERT ON item BEGIN CREATE TRIGGER item_ai AFTER INSERT ON item BEGIN
INSERT INTO item_search (rowid, content) VALUES (new.data->>'id', new.data->>'content'); INSERT INTO item_search (rowid, content) VALUES (new.data->>'id', new.data->>'content');
END; END;
SQL, [], $db); SQL, [], $pdo);
Custom::nonQuery(<<<'SQL' Custom::nonQuery(<<<'SQL'
CREATE TRIGGER item_au AFTER UPDATE ON item BEGIN CREATE TRIGGER item_au AFTER UPDATE ON item BEGIN
INSERT INTO item_search ( INSERT INTO item_search (
@ -42,7 +42,7 @@ class Data
); );
INSERT INTO item_search (rowid, content) VALUES (new.data->>'id', new.data->>'content'); INSERT INTO item_search (rowid, content) VALUES (new.data->>'id', new.data->>'content');
END; END;
SQL, [], $db); SQL, [], $pdo);
Custom::nonQuery(<<<'SQL' Custom::nonQuery(<<<'SQL'
CREATE TRIGGER item_ad AFTER DELETE ON item BEGIN CREATE TRIGGER item_ad AFTER DELETE ON item BEGIN
INSERT INTO item_search ( INSERT INTO item_search (
@ -51,7 +51,7 @@ class Data
'delete', old.data->>'id', old.data->>'content' 'delete', old.data->>'id', old.data->>'content'
); );
END; END;
SQL, [], $db); SQL, [], $pdo);
} }
/** /**
@ -62,23 +62,22 @@ class Data
public static function ensureDb(): void public static function ensureDb(): void
{ {
$tables = Custom::array("SELECT name FROM sqlite_master WHERE type = 'table'", [], new StringMapper('name')); $tables = Custom::array("SELECT name FROM sqlite_master WHERE type = 'table'", [], new StringMapper('name'));
$db = Configuration::dbConn(); $pdo = Configuration::dbConn();
if (!in_array(Table::USER, $tables)) { if (!in_array(Table::USER, $tables)) {
Definition::ensureTable(Table::USER, $db); Definition::ensureTable(Table::USER, $pdo);
Definition::ensureFieldIndex(Table::USER, 'email', ['email'], $db); Definition::ensureFieldIndex(Table::USER, 'email', ['email'], $pdo);
} }
if (!in_array(Table::FEED, $tables)) { if (!in_array(Table::FEED, $tables)) {
Definition::ensureTable(Table::FEED, $db); Definition::ensureTable(Table::FEED, $pdo);
Definition::ensureFieldIndex(Table::FEED, 'user', ['user_id'], $db); Definition::ensureFieldIndex(Table::FEED, 'user', ['user_id'], $pdo);
} }
if (!in_array(Table::ITEM, $tables)) { if (!in_array(Table::ITEM, $tables)) {
Definition::ensureTable(Table::ITEM, $db); Definition::ensureTable(Table::ITEM, $pdo);
Definition::ensureFieldIndex(Table::ITEM, 'feed', ['feed_id', 'item_link'], $db); Definition::ensureFieldIndex(Table::ITEM, 'feed', ['feed_id', 'item_link'], $pdo);
self::createSearchIndex($db); self::createSearchIndex($pdo);
} }
$journalMode = Custom::scalar("PRAGMA journal_mode", [], fn($it) => $it[0]); $journalMode = Custom::scalar("PRAGMA journal_mode", [], new StringMapper('journal_mode'));
if ($journalMode <> 'wal') Custom::nonQuery("PRAGMA journal_mode = 'wal'", []); if ($journalMode <> 'wal') Custom::nonQuery("PRAGMA journal_mode = 'wal'", []);
$db->close();
} }
/** /**
@ -103,7 +102,7 @@ class Data
*/ */
public static function feedField(int $feedId, string $qualifier = ''): Field public static function feedField(int $feedId, string $qualifier = ''): Field
{ {
$feedField = Field::EQ(Configuration::idField(), $feedId, '@feed'); $feedField = Field::EQ(Configuration::$idField, $feedId, '@feed');
$feedField->qualifier = $qualifier; $feedField->qualifier = $qualifier;
return $feedField; return $feedField;
} }

View File

@ -2,19 +2,19 @@
namespace FeedReaderCentral; namespace FeedReaderCentral;
use BitBadger\Documents\DocumentException; use BitBadger\PDODocument\Configuration;
use BitBadger\Documents\DocumentList; use BitBadger\PDODocument\Custom;
use BitBadger\Documents\Field; use BitBadger\PDODocument\Document;
use BitBadger\Documents\Query; use BitBadger\PDODocument\DocumentException;
use BitBadger\Documents\SQLite\Configuration; use BitBadger\PDODocument\DocumentList;
use BitBadger\Documents\SQLite\Custom; use BitBadger\PDODocument\Exists;
use BitBadger\Documents\SQLite\Document; use BitBadger\PDODocument\Field;
use BitBadger\Documents\SQLite\Exists; use BitBadger\PDODocument\Find;
use BitBadger\Documents\SQLite\Find; use BitBadger\PDODocument\Parameters;
use BitBadger\Documents\SQLite\Parameters; use BitBadger\PDODocument\Patch;
use BitBadger\Documents\SQLite\Patch; use BitBadger\PDODocument\Query;
use DateTimeInterface; use DateTimeInterface;
use SQLite3; use PDO;
/** /**
* An RSS or Atom feed * An RSS or Atom feed
@ -75,23 +75,23 @@ class Feed
* @param int $feedId The ID of the feed to which these items belong * @param int $feedId The ID of the feed to which these items belong
* @param ParsedFeed $parsed The extracted Atom or RSS feed items * @param ParsedFeed $parsed The extracted Atom or RSS feed items
* @param DateTimeInterface $lastChecked When this feed was last checked (only new items will be added) * @param DateTimeInterface $lastChecked When this feed was last checked (only new items will be added)
* @param PDO $pdo The database connection over which items should be updated
* @return array ['ok' => true] if successful, ['error' => message] if not * @return array ['ok' => true] if successful, ['error' => message] if not
*/ */
public static function updateItems(int $feedId, ParsedFeed $parsed, DateTimeInterface $lastChecked, public static function updateItems(int $feedId, ParsedFeed $parsed, DateTimeInterface $lastChecked, PDO $pdo): array
SQLite3 $db): array
{ {
$results = $results =
array_map(function ($item) use ($db, $feedId) { array_map(function ($item) use ($pdo, $feedId) {
try { try {
$existing = Find::firstByFields(Table::ITEM, $existing = Find::firstByFields(Table::ITEM,
[Field::EQ('feed_id', $feedId), Field::EQ('item_guid', $item->guid)], Item::class); [Field::EQ('feed_id', $feedId), Field::EQ('item_guid', $item->guid)], Item::class);
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(), $db); Patch::byId(Table::ITEM, $existing->id, $item->patchFields(), $pdo);
} }
} else { } else {
Document::insert(Table::ITEM, Item::fromFeedItem($feedId, $item), $db); Document::insert(Table::ITEM, Item::fromFeedItem($feedId, $item), $pdo);
} }
return ['ok' => true]; return ['ok' => true];
} catch (DocumentException $ex) { } catch (DocumentException $ex) {
@ -107,10 +107,10 @@ class Feed
* Purge items for a feed * Purge items for a feed
* *
* @param int $feedId The ID of the feed to be purged * @param int $feedId The ID of the feed to be purged
* @param SQLite3 $db The database connection on which items should be purged * @param PDO $pdo The database connection on which items should be purged
* @return array|string[]|true[] ['ok' => true] if purging was successful, ['error' => message] if not * @return array|string[]|true[] ['ok' => true] if purging was successful, ['error' => message] if not
*/ */
private static function purgeItems(int $feedId, SQLite3 $db): array private static function purgeItems(int $feedId, PDO $pdo): array
{ {
if (!array_search(PURGE_TYPE, [self::PURGE_READ, self::PURGE_BY_DAYS, self::PURGE_BY_COUNT])) { if (!array_search(PURGE_TYPE, [self::PURGE_READ, self::PURGE_BY_DAYS, self::PURGE_BY_COUNT])) {
return ['error' => 'Unrecognized purge type ' . PURGE_TYPE]; return ['error' => 'Unrecognized purge type ' . PURGE_TYPE];
@ -127,7 +127,7 @@ class Feed
$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;
$table = Table::ITEM; $table = Table::ITEM;
$sql .= ' ' . <<<SQL $sql .= ' ' . <<<SQL
AND data->>'$id' IN ( AND data->>'$id' IN (
@ -139,7 +139,7 @@ class Feed
SQL; SQL;
} }
try { try {
Custom::nonQuery($sql, Parameters::addFields($fields, []), $db); Custom::nonQuery($sql, Parameters::addFields($fields, []), $pdo);
return ['ok' => true]; return ['ok' => true];
} catch (DocumentException $ex) { } catch (DocumentException $ex) {
return ['error' => "$ex"]; return ['error' => "$ex"];
@ -151,10 +151,11 @@ class Feed
* *
* @param int $feedId The ID of the feed to be refreshed * @param int $feedId The ID of the feed to be refreshed
* @param string $url The URL of the feed to be refreshed * @param string $url The URL of the feed to be refreshed
* @param SQLite3 $db A database connection to use to refresh the feed * @param PDO $pdo A database connection to use to refresh the feed
* @return array|string[]|true[] ['ok' => true] if successful, ['error' => message] if not * @return array|string[]|true[] ['ok' => true] if successful, ['error' => message] if not
*/ */
public static function refreshFeed(int $feedId, string $url, SQLite3 $db): array { public static function refreshFeed(int $feedId, string $url, PDO $pdo): array
{
$feedRetrieval = ParsedFeed::retrieve($url); $feedRetrieval = ParsedFeed::retrieve($url);
if (key_exists('error', $feedRetrieval)) return $feedRetrieval; if (key_exists('error', $feedRetrieval)) return $feedRetrieval;
$feed = $feedRetrieval['ok']; $feed = $feedRetrieval['ok'];
@ -164,7 +165,7 @@ class Feed
if (!$feedDoc) return ['error' => 'Could not derive date last checked for feed']; if (!$feedDoc) return ['error' => 'Could not derive date last checked for feed'];
$lastChecked = date_create_immutable($feedDoc->checked_on ?? WWW_EPOCH); $lastChecked = date_create_immutable($feedDoc->checked_on ?? WWW_EPOCH);
$itemUpdate = self::updateItems($feedId, $feed, $lastChecked, $db); $itemUpdate = self::updateItems($feedId, $feed, $lastChecked, $pdo);
if (key_exists('error', $itemUpdate)) return $itemUpdate; if (key_exists('error', $itemUpdate)) return $itemUpdate;
$patch = [ $patch = [
@ -173,12 +174,12 @@ class Feed
'checked_on' => Data::formatDate('now') 'checked_on' => Data::formatDate('now')
]; ];
if ($url == $feed->url) $patch['url'] = $feed->url; if ($url == $feed->url) $patch['url'] = $feed->url;
Patch::byId(Table::FEED, $feedId, $patch, $db); Patch::byId(Table::FEED, $feedId, $patch, $pdo);
} catch (DocumentException $ex) { } catch (DocumentException $ex) {
return ['error' => "$ex"]; return ['error' => "$ex"];
} }
return PURGE_TYPE == self::PURGE_NONE ? ['ok' => true] : self::purgeItems($feedId, $db); return PURGE_TYPE == self::PURGE_NONE ? ['ok' => true] : self::purgeItems($feedId, $pdo);
} }
/** /**
@ -187,30 +188,32 @@ class Feed
* @param string $url The URL of the RSS feed to add * @param string $url The URL of the RSS feed to add
* @return array ['ok' => feedId] if successful, ['error' => message] if not * @return array ['ok' => feedId] if successful, ['error' => message] if not
*/ */
public static function add(string $url, SQLite3 $db): array { public static function add(string $url): array
{
$feedExtract = ParsedFeed::retrieve($url); $feedExtract = ParsedFeed::retrieve($url);
if (key_exists('error', $feedExtract)) return $feedExtract; if (key_exists('error', $feedExtract)) return $feedExtract;
$feed = $feedExtract['ok']; $feed = $feedExtract['ok'];
try { try {
$pdo = Configuration::dbConn();
$fields = [Field::EQ('user_id', $_SESSION[Key::USER_ID]), Field::EQ('url', $feed->url)]; $fields = [Field::EQ('user_id', $_SESSION[Key::USER_ID]), Field::EQ('url', $feed->url)];
if (Exists::byFields(Table::FEED, $fields, $db)) { if (Exists::byFields(Table::FEED, $fields, $pdo)) {
return ['error' => "Already subscribed to feed $feed->url"]; return ['error' => "Already subscribed to feed $feed->url"];
} }
Document::insert(Table::FEED, self::fromParsed($feed), $db); Document::insert(Table::FEED, self::fromParsed($feed), $pdo);
$doc = Find::firstByFields(Table::FEED, $fields, self::class); $doc = Find::firstByFields(Table::FEED, $fields, self::class);
if (!$doc) return ['error' => 'Could not retrieve inserted feed']; if (!$doc) return ['error' => 'Could not retrieve inserted feed'];
} catch (DocumentException $ex) {
return ['error' => "$ex"];
}
$result = self::updateItems($doc->id, $feed, date_create_immutable(WWW_EPOCH), $db); $result = self::updateItems($doc->id, $feed, date_create_immutable(WWW_EPOCH), $pdo);
if (key_exists('error', $result)) return $result; if (key_exists('error', $result)) return $result;
return ['ok' => $doc->id]; return ['ok' => $doc->id];
} catch (DocumentException $ex) {
return ['error' => "$ex"];
}
} }
/** /**
@ -218,19 +221,20 @@ class Feed
* *
* @param Feed $existing The existing feed * @param Feed $existing The existing feed
* @param string $url The URL with which the existing feed should be modified * @param string $url The URL with which the existing feed should be modified
* @param SQLite3 $db The database connection on which to execute the update
* @return bool[]|string[] [ 'ok' => true ] if successful, [ 'error' => message ] if not * @return bool[]|string[] [ 'ok' => true ] if successful, [ 'error' => message ] if not
*/ */
public static function update(Feed $existing, string $url, SQLite3 $db): array { public static function update(Feed $existing, string $url): array
{
try { try {
$pdo = Configuration::dbConn();
Patch::byFields(Table::FEED, Patch::byFields(Table::FEED,
[Field::EQ(Configuration::idField(), $existing->id), Field::EQ('user_id', $_SESSION[Key::USER_ID])], [Field::EQ(Configuration::$idField, $existing->id), Field::EQ('user_id', $_SESSION[Key::USER_ID])],
['url' => $url], $db); ['url' => $url], $pdo);
return self::refreshFeed($existing->id, $url, $pdo);
} catch (DocumentException $ex) { } catch (DocumentException $ex) {
return ['error' => "$ex"]; return ['error' => "$ex"];
} }
return self::refreshFeed($existing->id, $url, $db);
} }
/** /**
@ -250,18 +254,18 @@ class Feed
/** /**
* Refresh all feeds * Refresh all feeds
* *
* @param SQLite3 $db The database connection to use for refreshing feeds
* @return array|true[]|string[] ['ok' => true] if successful, * @return array|true[]|string[] ['ok' => true] if successful,
* ['error' => message] if not (may have multiple error lines) * ['error' => message] if not (may have multiple error lines)
*/ */
public static function refreshAll(SQLite3 $db): array public static function refreshAll(): array
{ {
try { try {
$feeds = self::retrieveAll($_SESSION[Key::USER_ID]); $feeds = self::retrieveAll($_SESSION[Key::USER_ID]);
$pdo = Configuration::dbConn();
$errors = []; $errors = [];
foreach ($feeds->items() as $feed) { foreach ($feeds->items() as $feed) {
$result = self::refreshFeed($feed->id, $feed->url, $db); $result = self::refreshFeed($feed->id, $feed->url, $pdo);
if (key_exists('error', $result)) $errors[] = $result['error']; if (key_exists('error', $result)) $errors[] = $result['error'];
} }
} catch (DocumentException $ex) { } catch (DocumentException $ex) {
@ -278,8 +282,9 @@ class Feed
* @return static|false The data for the feed if found, false if not found * @return static|false The data for the feed if found, false if not found
* @throws DocumentException If any is encountered * @throws DocumentException If any is encountered
*/ */
public static function retrieveById(int $feedId): static|false { public static function retrieveById(int $feedId): static|false
$doc = Find::byId(Table::FEED, $feedId, self::class); {
$doc = Find::byId(Table::FEED, $feedId, static::class);
return $doc && $doc->user_id == $_SESSION[Key::USER_ID] ? $doc : false; return $doc && $doc->user_id == $_SESSION[Key::USER_ID] ? $doc : false;
} }
} }

View File

@ -2,15 +2,14 @@
namespace FeedReaderCentral; namespace FeedReaderCentral;
use BitBadger\Documents\Configuration; use BitBadger\PDODocument\Configuration;
use BitBadger\Documents\DocumentException; use BitBadger\PDODocument\Custom;
use BitBadger\Documents\DocumentList; use BitBadger\PDODocument\DocumentException;
use BitBadger\Documents\Field; use BitBadger\PDODocument\DocumentList;
use BitBadger\Documents\JsonMapper; use BitBadger\PDODocument\Field;
use BitBadger\Documents\Query; use BitBadger\PDODocument\Parameters;
use BitBadger\Documents\SQLite\Custom; use BitBadger\PDODocument\Query;
use BitBadger\Documents\SQLite\Parameters; use BitBadger\PDODocument\Mapper\DocumentMapper;
use Iterator;
/** /**
* A list of items to be displayed * A list of items to be displayed
@ -61,7 +60,7 @@ class ItemList
ItemWithFeed::SELECT_WITH_FEED . ' WHERE ' ItemWithFeed::SELECT_WITH_FEED . ' WHERE '
. Query::whereByFields(array_filter($allFields, fn($it) => $it->paramName <> '@search')) . Query::whereByFields(array_filter($allFields, fn($it) => $it->paramName <> '@search'))
. $searchWhere, . $searchWhere,
Parameters::addFields($allFields, []), new JsonMapper(ItemWithFeed::class)); Parameters::addFields($allFields, []), new DocumentMapper(ItemWithFeed::class));
} catch (DocumentException $ex) { } catch (DocumentException $ex) {
$this->error = "$ex"; $this->error = "$ex";
} }
@ -141,7 +140,7 @@ class ItemList
if ($isBookmarked) $fields[] = Data::bookmarkField(Table::ITEM); if ($isBookmarked) $fields[] = Data::bookmarkField(Table::ITEM);
$list = new static('Matching' . ($isBookmarked ? ' Bookmarked' : ''), $list = new static('Matching' . ($isBookmarked ? ' Bookmarked' : ''),
"/search?search=$search&items=" . ($isBookmarked ? 'bookmarked' : 'all'), $fields, "/search?search=$search&items=" . ($isBookmarked ? 'bookmarked' : 'all'), $fields,
' AND ' . Table::ITEM . ".data->>'" . Configuration::idField() . "' IN " ' AND ' . Table::ITEM . ".data->>'" . Configuration::$idField . "' IN "
. '(SELECT ROWID FROM item_search WHERE content MATCH @search)'); . '(SELECT ROWID FROM item_search WHERE content MATCH @search)');
$list->showIndicators = true; $list->showIndicators = true;
$list->displayFeed = true; $list->displayFeed = true;

View File

@ -2,14 +2,14 @@
namespace FeedReaderCentral; namespace FeedReaderCentral;
use BitBadger\Documents\DocumentException; use BitBadger\PDODocument\Configuration;
use BitBadger\Documents\Field; use BitBadger\PDODocument\Custom;
use BitBadger\Documents\JsonMapper; use BitBadger\PDODocument\DocumentException;
use BitBadger\Documents\Query; use BitBadger\PDODocument\Field;
use BitBadger\Documents\SQLite\Configuration; use BitBadger\PDODocument\Parameters;
use BitBadger\Documents\SQLite\Custom; use BitBadger\PDODocument\Query;
use BitBadger\Documents\SQLite\Parameters; use BitBadger\PDODocument\Mapper\DocumentMapper;
use BitBadger\Documents\SQLite\Results; use BitBadger\PDODocument\Mapper\ExistsMapper;
/** /**
* A combined item and feed (used for lists) * A combined item and feed (used for lists)
@ -36,7 +36,7 @@ class ItemWithFeed extends Item
*/ */
private static function idAndUserFields(int $id): array private static function idAndUserFields(int $id): array
{ {
$idField = Field::EQ(Configuration::idField(), $id, '@id'); $idField = Field::EQ(Configuration::$idField, $id, '@id');
$idField->qualifier = Table::ITEM; $idField->qualifier = Table::ITEM;
$userField = Field::EQ('user_id', $_SESSION[Key::USER_ID], '@user'); $userField = Field::EQ('user_id', $_SESSION[Key::USER_ID], '@user');
$userField->qualifier = Table::FEED; $userField->qualifier = Table::FEED;
@ -54,7 +54,7 @@ class ItemWithFeed extends Item
{ {
$fields = self::idAndUserFields($id); $fields = self::idAndUserFields($id);
return Custom::scalar(Query\Exists::query(self::FROM_WITH_JOIN, Query::whereByFields($fields)), return Custom::scalar(Query\Exists::query(self::FROM_WITH_JOIN, Query::whereByFields($fields)),
Parameters::addFields($fields, []), Results::toExists(...)); Parameters::addFields($fields, []), new ExistsMapper());
} }
/** /**
@ -68,6 +68,6 @@ class ItemWithFeed extends Item
{ {
$fields = self::idAndUserFields($id); $fields = self::idAndUserFields($id);
return Custom::single(self::SELECT_WITH_FEED . ' WHERE ' . Query::whereByFields($fields), return Custom::single(self::SELECT_WITH_FEED . ' WHERE ' . Query::whereByFields($fields),
Parameters::addFields($fields, []), new JsonMapper(self::class)); Parameters::addFields($fields, []), new DocumentMapper(self::class));
} }
} }

View File

@ -2,9 +2,9 @@
namespace FeedReaderCentral; namespace FeedReaderCentral;
use BitBadger\Documents\DocumentException; use BitBadger\PDODocument\DocumentException;
use BitBadger\Documents\Field; use BitBadger\PDODocument\Field;
use BitBadger\Documents\SQLite\Patch; use BitBadger\PDODocument\Patch;
/** /**
* Security functions * Security functions

View File

@ -2,14 +2,14 @@
namespace FeedReaderCentral; namespace FeedReaderCentral;
use BitBadger\Documents\DocumentException; use BitBadger\PDODocument\Custom;
use BitBadger\Documents\Field; use BitBadger\PDODocument\Document;
use BitBadger\Documents\Query; use BitBadger\PDODocument\DocumentException;
use BitBadger\Documents\SQLite\Custom; use BitBadger\PDODocument\Field;
use BitBadger\Documents\SQLite\Document; use BitBadger\PDODocument\Find;
use BitBadger\Documents\SQLite\Find; use BitBadger\PDODocument\Mapper\ExistsMapper;
use BitBadger\Documents\SQLite\Parameters; use BitBadger\PDODocument\Parameters;
use BitBadger\Documents\SQLite\Results; use BitBadger\PDODocument\Query;
/** /**
* A user of Feed Reader Central * A user of Feed Reader Central
@ -59,6 +59,6 @@ class User
{ {
$fields = [Data::userIdField(Table::FEED), Data::bookmarkField(Table::ITEM)]; $fields = [Data::userIdField(Table::FEED), Data::bookmarkField(Table::ITEM)];
return Custom::scalar(Query\Exists::query(ItemWithFeed::FROM_WITH_JOIN, Query::whereByFields($fields)), return Custom::scalar(Query\Exists::query(ItemWithFeed::FROM_WITH_JOIN, Query::whereByFields($fields)),
Parameters::addFields($fields, []), Results::toExists(...)); Parameters::addFields($fields, []), new ExistsMapper());
} }
} }

View File

@ -6,8 +6,8 @@
* This will display a button which will either add or remove a bookmark for a given item. * This will display a button which will either add or remove a bookmark for a given item.
*/ */
use BitBadger\Documents\DocumentException; use BitBadger\PDODocument\DocumentException;
use BitBadger\Documents\SQLite\Patch; use BitBadger\PDODocument\Patch;
use FeedReaderCentral\ItemWithFeed; use FeedReaderCentral\ItemWithFeed;
use FeedReaderCentral\Table; use FeedReaderCentral\Table;

View File

@ -6,10 +6,9 @@
* Allows users to add, edit, and delete feeds * Allows users to add, edit, and delete feeds
*/ */
use BitBadger\Documents\DocumentException; use BitBadger\PDODocument\Delete;
use BitBadger\Documents\Field; use BitBadger\PDODocument\DocumentException;
use BitBadger\Documents\SQLite\Configuration; use BitBadger\PDODocument\Field;
use BitBadger\Documents\SQLite\Delete;
use FeedReaderCentral\Feed; use FeedReaderCentral\Feed;
use FeedReaderCentral\Security; use FeedReaderCentral\Security;
use FeedReaderCentral\Table; use FeedReaderCentral\Table;
@ -32,21 +31,19 @@ if ($_SERVER['REQUEST_METHOD'] == 'DELETE') {
} }
} }
$db = Configuration::dbConn();
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {
try { try {
$isNew = $_POST['id'] == 'new'; $isNew = $_POST['id'] == 'new';
if ($isNew) { if ($isNew) {
$result = Feed::add($_POST['url'], $db); $result = Feed::add($_POST['url']);
} else { } else {
$toEdit = Feed::retrieveById($_POST['id']); $toEdit = Feed::retrieveById($_POST['id']);
$result = $toEdit $result = $toEdit
? Feed::update($toEdit, $_POST['url'], $db) ? Feed::update($toEdit, $_POST['url'])
: ['error' => "Feed {$_POST['id']} not found"]; : ['error' => "Feed {$_POST['id']} not found"];
} }
if (key_exists('ok', $result)) { if (key_exists('ok', $result)) {
add_info('Feed saved successfully'); add_info('Feed saved successfully');
$db->close();
frc_redirect('/feeds'); frc_redirect('/feeds');
} }
add_error($result['error']); add_error($result['error']);
@ -83,4 +80,3 @@ page_head($title); ?>
</form> </form>
</article><?php </article><?php
page_foot(); page_foot();
$db->close();

View File

@ -6,11 +6,11 @@
* List feeds and provide links for maintenance actions * List feeds and provide links for maintenance actions
*/ */
use BitBadger\Documents\ArrayMapper; use BitBadger\PDODocument\Custom;
use BitBadger\Documents\Field; use BitBadger\PDODocument\Field;
use BitBadger\Documents\JsonMapper; use BitBadger\PDODocument\Mapper\ArrayMapper;
use BitBadger\Documents\Query; use BitBadger\PDODocument\Mapper\DocumentMapper;
use BitBadger\Documents\SQLite\Custom; use BitBadger\PDODocument\Query;
use FeedReaderCentral\Feed; use FeedReaderCentral\Feed;
use FeedReaderCentral\Key; use FeedReaderCentral\Key;
use FeedReaderCentral\Table; use FeedReaderCentral\Table;
@ -19,10 +19,9 @@ include '../start.php';
FeedReaderCentral\Security::verifyUser(); FeedReaderCentral\Security::verifyUser();
// TODO: adapt query when document list is done
$field = Field::EQ('user_id', $_SESSION[Key::USER_ID], '@user'); $field = Field::EQ('user_id', $_SESSION[Key::USER_ID], '@user');
$feeds = Custom::list(Query\Find::byFields(Table::FEED, [$field]) . " ORDER BY lower(data->>'title')", $feeds = Custom::list(Query\Find::byFields(Table::FEED, [$field]) . " ORDER BY lower(data->>'title')",
$field->appendParameter([]), new JsonMapper(Feed::class)); $field->appendParameter([]), new DocumentMapper(Feed::class));
page_head('Your Feeds'); ?> page_head('Your Feeds'); ?>
<h1>Your Feeds</h1> <h1>Your Feeds</h1>

View File

@ -6,7 +6,6 @@
* Displays a list of unread or bookmarked items for the current user * Displays a list of unread or bookmarked items for the current user
*/ */
use BitBadger\Documents\SQLite\Configuration;
use FeedReaderCentral\Feed; use FeedReaderCentral\Feed;
use FeedReaderCentral\ItemList; use FeedReaderCentral\ItemList;
@ -15,9 +14,7 @@ include '../start.php';
FeedReaderCentral\Security::verifyUser(); FeedReaderCentral\Security::verifyUser();
if (key_exists('refresh', $_GET)) { if (key_exists('refresh', $_GET)) {
$db = Configuration::dbConn(); $refreshResult = Feed::refreshAll();
$refreshResult = Feed::refreshAll($db);
$db->close();
if (key_exists('ok', $refreshResult)) { if (key_exists('ok', $refreshResult)) {
add_info('All feeds refreshed successfully'); add_info('All feeds refreshed successfully');
} else { } else {

View File

@ -6,9 +6,9 @@
* Retrieves and displays an item from a feed belonging to the current user * Retrieves and displays an item from a feed belonging to the current user
*/ */
use BitBadger\Documents\DocumentException; use BitBadger\PDODocument\DocumentException;
use BitBadger\Documents\SQLite\Delete; use BitBadger\PDODocument\Delete;
use BitBadger\Documents\SQLite\Patch; use BitBadger\PDODocument\Patch;
use FeedReaderCentral\ItemWithFeed; use FeedReaderCentral\ItemWithFeed;
use FeedReaderCentral\Table; use FeedReaderCentral\Table;

View File

@ -1,11 +1,11 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
use BitBadger\Documents\ArrayMapper; use BitBadger\PDODocument\Configuration;
use BitBadger\Documents\DocumentException; use BitBadger\PDODocument\Custom;
use BitBadger\Documents\SQLite\Configuration; use BitBadger\PDODocument\Document;
use BitBadger\Documents\SQLite\Custom; use BitBadger\PDODocument\DocumentException;
use BitBadger\Documents\SQLite\Document; use BitBadger\PDODocument\Mapper\ArrayMapper;
use BitBadger\Documents\SQLite\Results; use BitBadger\PDODocument\Mapper\ExistsMapper;
use FeedReaderCentral\Data; use FeedReaderCentral\Data;
use FeedReaderCentral\Feed; use FeedReaderCentral\Feed;
use FeedReaderCentral\Item; use FeedReaderCentral\Item;
@ -67,7 +67,7 @@ function check_status(): void
function run_update(): void function run_update(): void
{ {
try { try {
$db = Configuration::dbConn(); $pdo = Configuration::dbConn();
} catch (DocumentException $ex) { } catch (DocumentException $ex) {
printfn("ERR: Cannot obtain a connection to the database\n $ex"); printfn("ERR: Cannot obtain a connection to the database\n $ex");
return; return;
@ -75,51 +75,49 @@ function run_update(): void
try { try {
$searchExists = Custom::scalar("SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE name = 'item_ai')", [], $searchExists = Custom::scalar("SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE name = 'item_ai')", [],
Results::toExists(...)); new ExistsMapper());
if ($searchExists) { if ($searchExists) {
printfn('Removing search index...'); printfn('Removing search index...');
Custom::nonQuery('DROP TRIGGER item_ai', [], $db); Custom::nonQuery('DROP TRIGGER item_ai', [], $pdo);
Custom::nonQuery('DROP TRIGGER item_au', [], $db); Custom::nonQuery('DROP TRIGGER item_au', [], $pdo);
Custom::nonQuery('DROP TRIGGER item_ad', [], $db); Custom::nonQuery('DROP TRIGGER item_ad', [], $pdo);
Custom::nonQuery('DROP TABLE item_search', [], $db); Custom::nonQuery('DROP TABLE item_search', [], $pdo);
} }
printfn('Moving old tables...'); printfn('Moving old tables...');
Custom::nonQuery('ALTER TABLE item RENAME TO old_item', [], $db); Custom::nonQuery('ALTER TABLE item RENAME TO old_item', [], $pdo);
Custom::nonQuery('ALTER TABLE feed RENAME TO old_feed', [], $db); Custom::nonQuery('ALTER TABLE feed RENAME TO old_feed', [], $pdo);
Custom::nonQuery('ALTER TABLE frc_user RENAME TO old_user', [], $db); Custom::nonQuery('ALTER TABLE frc_user RENAME TO old_user', [], $pdo);
printfn('Creating new tables...'); printfn('Creating new tables...');
Data::ensureDb(); Data::ensureDb();
printfn('Migrating users...'); printfn('Migrating users...');
$users = $db->query('SELECT * FROM old_user'); $users = $pdo->query('SELECT * FROM old_user');
if (!$users) throw new DocumentException('Could not retrieve users'); if (!$users) throw new DocumentException('Could not retrieve users');
while ($user = $users->fetchArray(SQLITE3_ASSOC)) { while ($user = $users->fetch(PDO::FETCH_ASSOC)) {
Document::insert(Table::USER, new User($user['id'], $user['email'], $user['password']), $db); Document::insert(Table::USER, new User($user['id'], $user['email'], $user['password']), $pdo);
} }
printfn('Migrating feeds...'); printfn('Migrating feeds...');
$feeds = $db->query('SELECT * FROM old_feed'); $feeds = $pdo->query('SELECT * FROM old_feed');
if (!$feeds) throw new DocumentException('Could not retrieve feeds'); if (!$feeds) throw new DocumentException('Could not retrieve feeds');
while ($feed = $feeds->fetchArray(SQLITE3_ASSOC)) { while ($feed = $feeds->fetch(PDO::FETCH_ASSOC)) {
Document::insert(Table::FEED, Document::insert(Table::FEED,
new Feed($feed['id'], $feed['user_id'], $feed['url'], $feed['title'], $feed['updated_on'], new Feed($feed['id'], $feed['user_id'], $feed['url'], $feed['title'], $feed['updated_on'],
$feed['checked_on']), $db); $feed['checked_on']), $pdo);
} }
printfn('Migrating items...'); printfn('Migrating items...');
$items = $db->query('SELECT * FROM old_item'); $items = $pdo->query('SELECT * FROM old_item');
if (!$items) throw new DocumentException('Could not retrieve items'); if (!$items) throw new DocumentException('Could not retrieve items');
while ($item = $items->fetchArray(SQLITE3_ASSOC)) { while ($item = $items->fetch(PDO::FETCH_ASSOC)) {
Document::insert(Table::ITEM, Document::insert(Table::ITEM,
new Item($item['id'], $item['feed_id'], $item['title'], $item['item_guid'], $item['item_link'], new Item($item['id'], $item['feed_id'], $item['title'], $item['item_guid'], $item['item_link'],
$item['published_on'], $item['updated_on'], $item['content'], $item['is_read'], $item['published_on'], $item['updated_on'], $item['content'], $item['is_read'],
$item['is_bookmarked']), $db); $item['is_bookmarked']), $pdo);
} }
printfn('Dropping old tables...'); printfn('Dropping old tables...');
Custom::nonQuery('DROP TABLE old_item', [], $db); Custom::nonQuery('DROP TABLE old_item', [], $pdo);
Custom::nonQuery('DROP TABLE old_feed', [], $db); Custom::nonQuery('DROP TABLE old_feed', [], $pdo);
Custom::nonQuery('DROP TABLE old_user', [], $db); Custom::nonQuery('DROP TABLE old_user', [], $pdo);
printfn(PHP_EOL. 'Migration complete!'); printfn(PHP_EOL. 'Migration complete!');
} catch (DocumentException $ex) { } catch (DocumentException $ex) {
printfn("ERR $ex"); printfn("ERR $ex");
} finally {
$db->close();
} }
} }

View File

@ -1,8 +1,8 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
use BitBadger\Documents\DocumentException; use BitBadger\PDODocument\Configuration;
use BitBadger\Documents\SQLite\Configuration; use BitBadger\PDODocument\DocumentException;
use BitBadger\Documents\SQLite\Find; use BitBadger\PDODocument\Find;
use FeedReaderCentral\Feed; use FeedReaderCentral\Feed;
use FeedReaderCentral\Table; use FeedReaderCentral\Table;
use FeedReaderCentral\User; use FeedReaderCentral\User;
@ -37,7 +37,7 @@ function display_help(): never
function refresh_all(): void function refresh_all(): void
{ {
try { try {
$db = Configuration::dbConn(); $pdo = Configuration::dbConn();
} catch (DocumentException $ex) { } catch (DocumentException $ex) {
printfn("ERR: Cannot obtain a connection to the database\n $ex"); printfn("ERR: Cannot obtain a connection to the database\n $ex");
return; return;
@ -45,8 +45,8 @@ function refresh_all(): void
try { try {
$users = []; $users = [];
iterator_apply(Feed::retrieveAll()->items(), function (Feed $feed) use ($db, &$users) { iterator_apply(Feed::retrieveAll()->items(), function (Feed $feed) use ($pdo, &$users) {
$result = Feed::refreshFeed($feed->id, $feed->url, $db); $result = Feed::refreshFeed($feed->id, $feed->url, $pdo);
$userKey = "$feed->user_id"; $userKey = "$feed->user_id";
if (!key_exists($userKey, $users)) $users[$userKey] = Find::byId(Table::USER, $feed->user_id, User::class); if (!key_exists($userKey, $users)) $users[$userKey] = Find::byId(Table::USER, $feed->user_id, User::class);
if (array_key_exists('error', $result)) { if (array_key_exists('error', $result)) {
@ -61,7 +61,4 @@ function refresh_all(): void
printfn("ERR $ex"); printfn("ERR $ex");
return; return;
} }
finally {
$db->close();
}
} }

View File

@ -1,9 +1,9 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
use BitBadger\Documents\DocumentException; use BitBadger\PDODocument\Configuration;
use BitBadger\Documents\SQLite\Configuration; use BitBadger\PDODocument\Custom;
use BitBadger\Documents\SQLite\Custom; use BitBadger\PDODocument\DocumentException;
use BitBadger\Documents\SQLite\Results; use BitBadger\PDODocument\Mapper\CountMapper;
use FeedReaderCentral\Data; use FeedReaderCentral\Data;
require __DIR__ . '/../cli-start.php'; require __DIR__ . '/../cli-start.php';
@ -39,25 +39,23 @@ function display_help(): never
function rebuild_index(): void function rebuild_index(): void
{ {
try { try {
$db = Configuration::dbConn(); $pdo = Configuration::dbConn();
} catch (DocumentException $ex) { } catch (DocumentException $ex) {
printfn("ERR: Cannot obtain a connection to the database\n $ex"); printfn("ERR: Cannot obtain a connection to the database\n $ex");
return; return;
} }
try { try {
$hasIndex = Custom::scalar("SELECT COUNT(*) FROM sqlite_master WHERE name = 'item_ai'", [], $hasIndex = Custom::scalar("SELECT COUNT(*) FROM sqlite_master WHERE name = 'item_ai'", [], new CountMapper(),
Results::toCount(...), $db); $pdo);
if ($hasIndex == 0) { if ($hasIndex == 0) {
printfn('Creating search index....'); printfn('Creating search index....');
Data::createSearchIndex($db); Data::createSearchIndex($pdo);
} }
printfn('Rebuilding search index...'); printfn('Rebuilding search index...');
Custom::nonQuery("INSERT INTO item_search (item_search) VALUES ('rebuild')", [], $db); Custom::nonQuery("INSERT INTO item_search (item_search) VALUES ('rebuild')", [], $pdo);
printfn(PHP_EOL . 'Search index rebuilt'); printfn(PHP_EOL . 'Search index rebuilt');
} catch (DocumentException $ex) { } catch (DocumentException $ex) {
printfn("$ex"); printfn("$ex");
} finally {
$db->close();
} }
} }

View File

@ -1,14 +1,14 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
use BitBadger\Documents\DocumentException; use BitBadger\PDODocument\DocumentException;
use BitBadger\Documents\Field; use BitBadger\PDODocument\Field;
use BitBadger\Documents\Query; use BitBadger\PDODocument\Query;
use BitBadger\Documents\SQLite\Configuration; use BitBadger\PDODocument\Configuration;
use BitBadger\Documents\SQLite\Count; use BitBadger\PDODocument\Count;
use BitBadger\Documents\SQLite\Custom; use BitBadger\PDODocument\Custom;
use BitBadger\Documents\SQLite\Delete; use BitBadger\PDODocument\Delete;
use BitBadger\Documents\SQLite\Parameters; use BitBadger\PDODocument\Parameters;
use BitBadger\Documents\SQLite\Patch; use BitBadger\PDODocument\Patch;
use FeedReaderCentral\Security; use FeedReaderCentral\Security;
use FeedReaderCentral\Table; use FeedReaderCentral\Table;
use FeedReaderCentral\User; use FeedReaderCentral\User;