Documents and Documentation (beta 1) #23
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,4 +1,4 @@
|
||||||
.idea
|
.idea
|
||||||
vendor
|
vendor
|
||||||
src/data/*.db
|
src/data/*.db*
|
||||||
src/user-config.php
|
src/user-config.php
|
||||||
|
|
75
src/composer.lock
generated
75
src/composer.lock
generated
|
@ -12,7 +12,10 @@
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.bitbadger.solutions/bit-badger/documents-common",
|
"url": "https://git.bitbadger.solutions/bit-badger/documents-common",
|
||||||
"reference": "36e66969a20ba7e20d6a68e810181522c6724d9f"
|
"reference": "9dd8059e80423c67c79c62ff27e92f920d5f041b"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"netresearch/jsonmapper": "^4"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^11"
|
"phpunit/phpunit": "^11"
|
||||||
|
@ -24,7 +27,7 @@
|
||||||
"BitBadger\\Documents\\Query\\": "./Query"
|
"BitBadger\\Documents\\Query\\": "./Query"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"time": "2024-06-03T01:55:12+00:00"
|
"time": "2024-06-03T15:26:37+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bit-badger/documents-sqlite",
|
"name": "bit-badger/documents-sqlite",
|
||||||
|
@ -32,20 +35,80 @@
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.bitbadger.solutions/bit-badger/documents-sqlite",
|
"url": "https://git.bitbadger.solutions/bit-badger/documents-sqlite",
|
||||||
"reference": "3761397ea5e286f5f3858bb1a5266adee6d92a25"
|
"reference": "1439742c40ee8b8accd8d70f2ce94df458c6bc42"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"bit-badger/documents-common": "dev-conversion",
|
"bit-badger/documents-common": "dev-conversion",
|
||||||
"ext-sqlite3": "*"
|
"ext-sqlite3": "*"
|
||||||
},
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^11"
|
||||||
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"BitBadger\\Documents\\SQLite\\": "./",
|
"BitBadger\\Documents\\SQLite\\": "./src",
|
||||||
"BitBadger\\Documents\\SQLite\\Query\\": "./Query"
|
"BitBadger\\Documents\\SQLite\\Query\\": "./src/Query"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"time": "2024-06-03T01:56:39+00:00"
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Test\\Unit\\": "./tests/unit",
|
||||||
|
"Test\\Integration\\": "./tests/integration"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"time": "2024-06-03T22:16:08+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "netresearch/jsonmapper",
|
||||||
|
"version": "v4.4.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/cweiske/jsonmapper.git",
|
||||||
|
"reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/132c75c7dd83e45353ebb9c6c9f591952995bbf0",
|
||||||
|
"reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"ext-pcre": "*",
|
||||||
|
"ext-reflection": "*",
|
||||||
|
"ext-spl": "*",
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0",
|
||||||
|
"squizlabs/php_codesniffer": "~3.5"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": {
|
||||||
|
"JsonMapper": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"OSL-3.0"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Christian Weiske",
|
||||||
|
"email": "cweiske@cweiske.de",
|
||||||
|
"homepage": "http://github.com/cweiske/jsonmapper/",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Map nested JSON structures onto PHP classes",
|
||||||
|
"support": {
|
||||||
|
"email": "cweiske@cweiske.de",
|
||||||
|
"issues": "https://github.com/cweiske/jsonmapper/issues",
|
||||||
|
"source": "https://github.com/cweiske/jsonmapper/tree/v4.4.1"
|
||||||
|
},
|
||||||
|
"time": "2024-01-31T06:18:54+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [],
|
"packages-dev": [],
|
||||||
|
|
|
@ -76,6 +76,8 @@ class Data
|
||||||
Definition::ensureFieldIndex(Table::ITEM, 'feed', ['feed_id', 'item_link'], $db);
|
Definition::ensureFieldIndex(Table::ITEM, 'feed', ['feed_id', 'item_link'], $db);
|
||||||
self::createSearchIndex($db);
|
self::createSearchIndex($db);
|
||||||
}
|
}
|
||||||
|
$journalMode = Custom::scalar("PRAGMA journal_mode", [], fn($it) => $it[0]);
|
||||||
|
if ($journalMode <> 'wal') Custom::nonQuery("PRAGMA journal_mode = 'wal'", []);
|
||||||
$db->close();
|
$db->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ use BitBadger\Documents\JsonMapper;
|
||||||
use BitBadger\Documents\Query;
|
use BitBadger\Documents\Query;
|
||||||
use BitBadger\Documents\SQLite\Custom;
|
use BitBadger\Documents\SQLite\Custom;
|
||||||
use BitBadger\Documents\SQLite\Parameters;
|
use BitBadger\Documents\SQLite\Parameters;
|
||||||
|
use Iterator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of items to be displayed
|
* A list of items to be displayed
|
||||||
|
@ -140,7 +141,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,
|
||||||
' ' . 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;
|
||||||
|
@ -159,8 +160,7 @@ class ItemList
|
||||||
$return = $this->returnURL == '' ? '' : '&from=' . urlencode($this->returnURL);
|
$return = $this->returnURL == '' ? '' : '&from=' . urlencode($this->returnURL);
|
||||||
$hasItems = false;
|
$hasItems = false;
|
||||||
echo '<article>';
|
echo '<article>';
|
||||||
iterator_apply($this->dbList->items(), function (ItemWithFeed $it) use (&$hasItems, $return)
|
foreach ($this->dbList->items() as $it) {
|
||||||
{
|
|
||||||
$hasItems = true;
|
$hasItems = true;
|
||||||
echo '<p>' . hx_get("/item?id=$it->id$return", strip_tags($it->title)) . '<br><small>';
|
echo '<p>' . hx_get("/item?id=$it->id$return", strip_tags($it->title)) . '<br><small>';
|
||||||
if ($this->showIndicators) {
|
if ($this->showIndicators) {
|
||||||
|
@ -176,7 +176,7 @@ class ItemList
|
||||||
echo ' • ' . htmlentities($it->feed->title);
|
echo ' • ' . htmlentities($it->feed->title);
|
||||||
}
|
}
|
||||||
echo '</small>';
|
echo '</small>';
|
||||||
});
|
}
|
||||||
if (!$hasItems) {
|
if (!$hasItems) {
|
||||||
echo '<p><em>There are no ' . strtolower($this->itemType) . ' items</em>';
|
echo '<p><em>There are no ' . strtolower($this->itemType) . ' items</em>';
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ class ItemWithFeed extends Item
|
||||||
|
|
||||||
/** @var string The `SELECT` clause to add the feed as a property to the item's document */
|
/** @var string The `SELECT` clause to add the feed as a property to the item's document */
|
||||||
public const string SELECT_WITH_FEED =
|
public const string SELECT_WITH_FEED =
|
||||||
'SELECT json_set(' . Table::ITEM . ".data, '$.feed', " . Table::FEED . '.data) AS data FROM '
|
'SELECT json_set(' . Table::ITEM . ".data, '$.feed', json(" . Table::FEED . '.data)) AS data FROM '
|
||||||
. self::FROM_WITH_JOIN;
|
. self::FROM_WITH_JOIN;
|
||||||
|
|
||||||
/** @var Feed The feed to which this item belongs */
|
/** @var Feed The feed to which this item belongs */
|
||||||
|
|
|
@ -68,7 +68,6 @@ class Security
|
||||||
$dbEmail = $email;
|
$dbEmail = $email;
|
||||||
}
|
}
|
||||||
$user = User::findByEmail($dbEmail);
|
$user = User::findByEmail($dbEmail);
|
||||||
var_dump($user);
|
|
||||||
if ($user) self::verifyPassword($user, $password, $returnTo);
|
if ($user) self::verifyPassword($user, $password, $returnTo);
|
||||||
add_error('Invalid credentials; log on unsuccessful');
|
add_error('Invalid credentials; log on unsuccessful');
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ include '../start.php';
|
||||||
|
|
||||||
FeedReaderCentral\Security::verifyUser();
|
FeedReaderCentral\Security::verifyUser();
|
||||||
|
|
||||||
$id = $_GET['id'];
|
$id = key_exists('id', $_GET) ? (int)$_GET['id'] : -1;
|
||||||
|
|
||||||
if (!$item = ItemWithFeed::retrieveById($id)) not_found();
|
if (!$item = ItemWithFeed::retrieveById($id)) not_found();
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@ include '../../start.php';
|
||||||
|
|
||||||
FeedReaderCentral\Security::verifyUser();
|
FeedReaderCentral\Security::verifyUser();
|
||||||
|
|
||||||
if (!($feed = Feed::retrieveById($_GET['id']))) not_found();
|
$id = key_exists('id', $_GET) ? (int)$_GET['id'] : -1;
|
||||||
|
if (!($feed = Feed::retrieveById($id))) not_found();
|
||||||
|
|
||||||
$list = match (true) {
|
$list = match (true) {
|
||||||
key_exists('unread', $_GET) => ItemList::unreadForFeed($feed->id),
|
key_exists('unread', $_GET) => ItemList::unreadForFeed($feed->id),
|
||||||
|
|
|
@ -22,13 +22,13 @@ FeedReaderCentral\Security::verifyUser();
|
||||||
// TODO: adapt query when document list is done
|
// 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->toParameter(), new JsonMapper(Feed::class));
|
$field->appendParameter([]), new JsonMapper(Feed::class));
|
||||||
|
|
||||||
page_head('Your Feeds'); ?>
|
page_head('Your Feeds'); ?>
|
||||||
<h1>Your Feeds</h1>
|
<h1>Your Feeds</h1>
|
||||||
<article>
|
<article>
|
||||||
<p class=action_buttons><?=hx_get('/feed/?id=new', 'Add Feed')?></p><?php
|
<p class=action_buttons><?=hx_get('/feed/?id=new', 'Add Feed')?></p><?php
|
||||||
iterator_apply($feeds->items(), function (Feed $feed) {
|
foreach ($feeds->items() as /** @var Feed $feed */ $feed) {
|
||||||
$item = Table::ITEM;
|
$item = Table::ITEM;
|
||||||
$counts = Custom::single(<<<SQL
|
$counts = Custom::single(<<<SQL
|
||||||
SELECT (SELECT COUNT(*) FROM $item WHERE data->>'feed_id' = @feed) AS total,
|
SELECT (SELECT COUNT(*) FROM $item WHERE data->>'feed_id' = @feed) AS total,
|
||||||
|
@ -47,6 +47,6 @@ page_head('Your Feeds'); ?>
|
||||||
<a href=/feed/?id=<?=$feed->id?> hx-delete=/feed/?id=<?=$feed->id?>
|
<a href=/feed/?id=<?=$feed->id?> hx-delete=/feed/?id=<?=$feed->id?>
|
||||||
hx-confirm="Are you sure you want to delete “<?=htmlspecialchars($feed->title)?>”? This will remove the feed and all its items, including unread and bookmarked.">Delete</a>
|
hx-confirm="Are you sure you want to delete “<?=htmlspecialchars($feed->title)?>”? This will remove the feed and all its items, including unread and bookmarked.">Delete</a>
|
||||||
</span><?php
|
</span><?php
|
||||||
}); ?>
|
} ?>
|
||||||
</article><?php
|
</article><?php
|
||||||
page_foot();
|
page_foot();
|
||||||
|
|
|
@ -16,11 +16,17 @@ include '../start.php';
|
||||||
|
|
||||||
FeedReaderCentral\Security::verifyUser();
|
FeedReaderCentral\Security::verifyUser();
|
||||||
|
|
||||||
|
$id = match (true) {
|
||||||
|
key_exists('id', $_POST) => (int)$_POST['id'],
|
||||||
|
key_exists('id', $_GET) => (int)$_GET['id'],
|
||||||
|
default => -1
|
||||||
|
};
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
try {
|
try {
|
||||||
// "Keep as New" button sends a POST request to reset the is_read flag before going back to the item list
|
// "Keep as New" button sends a POST request to reset the is_read flag before going back to the item list
|
||||||
if (ItemWithFeed::existsById($_POST['id'])) {
|
if (ItemWithFeed::existsById($id)) {
|
||||||
Patch::byId(Table::ITEM, $_POST['id'], ['is_read' => 0]);
|
Patch::byId(Table::ITEM, $id, ['is_read' => 0]);
|
||||||
}
|
}
|
||||||
frc_redirect($_POST['from']);
|
frc_redirect($_POST['from']);
|
||||||
} catch (DocumentException $ex) {
|
} catch (DocumentException $ex) {
|
||||||
|
@ -32,8 +38,8 @@ $from = $_GET['from'] ?? '/';
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] == 'DELETE') {
|
if ($_SERVER['REQUEST_METHOD'] == 'DELETE') {
|
||||||
try {
|
try {
|
||||||
if (ItemWithFeed::existsById($_GET['id'])) {
|
if (ItemWithFeed::existsById($id)) {
|
||||||
Delete::byId(Table::ITEM, $_GET['id']);
|
Delete::byId(Table::ITEM, $id);
|
||||||
}
|
}
|
||||||
} catch (DocumentException $ex) {
|
} catch (DocumentException $ex) {
|
||||||
add_error("$ex");
|
add_error("$ex");
|
||||||
|
@ -41,10 +47,9 @@ if ($_SERVER['REQUEST_METHOD'] == 'DELETE') {
|
||||||
frc_redirect($from);
|
frc_redirect($from);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$item = ItemWithFeed::retrieveById($_GET['id'])) not_found();
|
if (!$item = ItemWithFeed::retrieveById($id)) not_found();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Patch::byId(Table::ITEM, $_GET['id'], ['is_read' => 1]);
|
Patch::byId(Table::ITEM, $id, ['is_read' => 1]);
|
||||||
} catch (DocumentException $ex) {
|
} catch (DocumentException $ex) {
|
||||||
add_error("$ex");
|
add_error("$ex");
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@ session_start([
|
||||||
'cookie_httponly' => true,
|
'cookie_httponly' => true,
|
||||||
'cookie_samesite' => 'Strict']);
|
'cookie_samesite' => 'Strict']);
|
||||||
|
|
||||||
|
//const DOC_DEBUG_SQL = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a message to be displayed at the top of the page
|
* Add a message to be displayed at the top of the page
|
||||||
*
|
*
|
||||||
|
|
|
@ -5,6 +5,7 @@ use BitBadger\Documents\DocumentException;
|
||||||
use BitBadger\Documents\SQLite\Configuration;
|
use BitBadger\Documents\SQLite\Configuration;
|
||||||
use BitBadger\Documents\SQLite\Custom;
|
use BitBadger\Documents\SQLite\Custom;
|
||||||
use BitBadger\Documents\SQLite\Document;
|
use BitBadger\Documents\SQLite\Document;
|
||||||
|
use BitBadger\Documents\SQLite\Results;
|
||||||
use FeedReaderCentral\Data;
|
use FeedReaderCentral\Data;
|
||||||
use FeedReaderCentral\Feed;
|
use FeedReaderCentral\Feed;
|
||||||
use FeedReaderCentral\Item;
|
use FeedReaderCentral\Item;
|
||||||
|
@ -73,11 +74,15 @@ function run_update(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
$searchExists = Custom::scalar("SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE name = 'item_ai')", [],
|
||||||
|
Results::toExists(...));
|
||||||
|
if ($searchExists) {
|
||||||
printfn('Removing search index...');
|
printfn('Removing search index...');
|
||||||
Custom::nonQuery('DROP TRIGGER item_ai', [], $db);
|
Custom::nonQuery('DROP TRIGGER item_ai', [], $db);
|
||||||
Custom::nonQuery('DROP TRIGGER item_au', [], $db);
|
Custom::nonQuery('DROP TRIGGER item_au', [], $db);
|
||||||
Custom::nonQuery('DROP TRIGGER item_ad', [], $db);
|
Custom::nonQuery('DROP TRIGGER item_ad', [], $db);
|
||||||
Custom::nonQuery('DROP TABLE item_search', [], $db);
|
Custom::nonQuery('DROP TABLE item_search', [], $db);
|
||||||
|
}
|
||||||
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', [], $db);
|
||||||
Custom::nonQuery('ALTER TABLE feed RENAME TO old_feed', [], $db);
|
Custom::nonQuery('ALTER TABLE feed RENAME TO old_feed', [], $db);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user