Documents and Documentation (beta 1) (#23)
- Change to SQLite document store - Complete documentation on usage of Feed Reader Central - Update INSTALLING.md for new installation procedures Reviewed-on: #23
This commit was merged in pull request #23.
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
use BitBadger\PDODocument\{Custom, Document, DocumentException};
|
||||
use BitBadger\PDODocument\Mapper\{ArrayMapper, ExistsMapper};
|
||||
use FeedReaderCentral\{Data, Feed, Item, Table, User};
|
||||
|
||||
require __DIR__ . '/../cli-start.php';
|
||||
|
||||
cli_title('DATABASE UPDATE');
|
||||
|
||||
//const PDO_DOC_DEBUG_SQL = true;
|
||||
|
||||
if ($argc < 2) display_help();
|
||||
|
||||
switch ($argv[1]) {
|
||||
case 'check':
|
||||
check_status();
|
||||
break;
|
||||
case 'run':
|
||||
run_update();
|
||||
break;
|
||||
default:
|
||||
printfn('Unrecognized option "%s"', $argv[1]);
|
||||
display_help();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display the options for this utility and exit
|
||||
*/
|
||||
function display_help(): never
|
||||
{
|
||||
printfn('Options:');
|
||||
printfn(' - check');
|
||||
printfn(' Check to see if the configured database has been updated');
|
||||
printfn(' - run');
|
||||
printfn(' Run the beta1 database storage update');
|
||||
exit(0);
|
||||
}
|
||||
|
||||
function json_column_exists(): bool
|
||||
{
|
||||
try {
|
||||
$table = Custom::single("SELECT sql FROM sqlite_master WHERE tbl_name='frc_user'", [], new ArrayMapper());
|
||||
return $table && substr_compare(strtolower($table['sql']), 'data text not null', 0) >= 0;
|
||||
} catch (DocumentException $ex) {
|
||||
printfn("ERR $ex");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function check_status(): void
|
||||
{
|
||||
if (json_column_exists()) {
|
||||
printfn('The database has already been updated');
|
||||
} else {
|
||||
printfn('The database has yet to be updated');
|
||||
}
|
||||
}
|
||||
|
||||
function run_update(): void
|
||||
{
|
||||
try {
|
||||
$searchExists = Custom::scalar("SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE name = 'item_ai')", [],
|
||||
new ExistsMapper());
|
||||
if ($searchExists) {
|
||||
printfn('Removing search index...');
|
||||
Custom::nonQuery('DROP TRIGGER item_ai', []);
|
||||
Custom::nonQuery('DROP TRIGGER item_au', []);
|
||||
Custom::nonQuery('DROP TRIGGER item_ad', []);
|
||||
Custom::nonQuery('DROP TABLE item_search', []);
|
||||
}
|
||||
printfn('Moving old tables...');
|
||||
Custom::nonQuery('ALTER TABLE item RENAME TO old_item', []);
|
||||
Custom::nonQuery('ALTER TABLE feed RENAME TO old_feed', []);
|
||||
Custom::nonQuery('ALTER TABLE frc_user RENAME TO old_user', []);
|
||||
printfn('Creating new tables...');
|
||||
Data::ensureDb();
|
||||
printfn('Migrating users...');
|
||||
$users = Custom::list('SELECT * FROM old_user', [], new ArrayMapper());
|
||||
if (!$users->hasItems()) throw new DocumentException('Could not retrieve users');
|
||||
foreach ($users->items() as $user) {
|
||||
Document::insert(Table::USER, new User($user['id'], $user['email'], $user['password']));
|
||||
}
|
||||
printfn('Migrating feeds...');
|
||||
$feeds = Custom::list('SELECT * FROM old_feed', [], new ArrayMapper());
|
||||
if (!$feeds->hasItems()) throw new DocumentException('Could not retrieve feeds');
|
||||
foreach ($feeds->items() as $feed) {
|
||||
Document::insert(Table::FEED,
|
||||
new Feed($feed['id'], $feed['user_id'], $feed['url'], $feed['title'], $feed['updated_on'],
|
||||
$feed['checked_on']));
|
||||
}
|
||||
printfn('Migrating items...');
|
||||
$items = Custom::list('SELECT * FROM old_item', [], new ArrayMapper());
|
||||
if (!$items->hasItems()) throw new DocumentException('Could not retrieve items');
|
||||
foreach ($items->items() as $item) {
|
||||
Document::insert(Table::ITEM,
|
||||
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['is_bookmarked']));
|
||||
}
|
||||
printfn('Dropping old tables...');
|
||||
Custom::nonQuery('DROP TABLE old_item', []);
|
||||
Custom::nonQuery('DROP TABLE old_feed', []);
|
||||
Custom::nonQuery('DROP TABLE old_user', []);
|
||||
printfn(PHP_EOL. 'Migration complete!');
|
||||
} catch (DocumentException $ex) {
|
||||
printfn("ERR $ex");
|
||||
}
|
||||
}
|
||||
+18
-18
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
use JetBrains\PhpStorm\NoReturn;
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
use BitBadger\PDODocument\{DocumentException, Find};
|
||||
use FeedReaderCentral\{Feed, Table, User};
|
||||
|
||||
require __DIR__ . '/../cli-start.php';
|
||||
|
||||
@@ -20,34 +22,32 @@ switch ($argv[1]) {
|
||||
/**
|
||||
* Display the options for this utility and exit
|
||||
*/
|
||||
#[NoReturn]
|
||||
function display_help(): void {
|
||||
function display_help(): never
|
||||
{
|
||||
printfn('Options:');
|
||||
printfn(' - all');
|
||||
printfn(' Refreshes all feeds');
|
||||
exit(0);
|
||||
}
|
||||
|
||||
function refresh_all(): void {
|
||||
$db = Data::getConnection();
|
||||
|
||||
function refresh_all(): void
|
||||
{
|
||||
try {
|
||||
$feeds = Feed::retrieveAll($db);
|
||||
if (array_key_exists('error', $feeds)) {
|
||||
printfn('SQLite error: %s', $feeds['error']);
|
||||
return;
|
||||
}
|
||||
array_walk($feeds, function ($feed) use ($db) {
|
||||
$result = Feed::refreshFeed($feed['id'], $feed['url'], $db);
|
||||
$users = [];
|
||||
iterator_apply(Feed::retrieveAll()->items(), function (Feed $feed) use (&$users) {
|
||||
$result = Feed::refreshFeed($feed->id, $feed->url);
|
||||
$userKey = "$feed->user_id";
|
||||
if (!key_exists($userKey, $users)) $users[$userKey] = Find::byId(Table::USER, $feed->user_id, User::class);
|
||||
if (array_key_exists('error', $result)) {
|
||||
printfn('ERR (%s) %s', $feed['email'], $feed['url']);
|
||||
printfn('ERR (%s) %s', $users[$userKey]->email, $feed->url);
|
||||
printfn(' %s', $result['error']);
|
||||
} else {
|
||||
printfn('OK (%s) %s', $feed['email'], $feed['url']);
|
||||
printfn('OK (%s) %s', $users[$userKey]->email, $feed->url);
|
||||
}
|
||||
});
|
||||
printfn(PHP_EOL . 'All feeds refreshed');
|
||||
} finally {
|
||||
$db->close();
|
||||
} catch (DocumentException $ex) {
|
||||
printfn("ERR $ex");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
+16
-13
@@ -1,5 +1,8 @@
|
||||
<?php
|
||||
use JetBrains\PhpStorm\NoReturn;
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
use BitBadger\PDODocument\{Custom, DocumentException};
|
||||
use BitBadger\PDODocument\Mapper\ExistsMapper;
|
||||
use FeedReaderCentral\Data;
|
||||
|
||||
require __DIR__ . '/../cli-start.php';
|
||||
|
||||
@@ -20,8 +23,8 @@ switch ($argv[1]) {
|
||||
/**
|
||||
* Display the options for this utility and exit
|
||||
*/
|
||||
#[NoReturn]
|
||||
function display_help(): void {
|
||||
function display_help(): never
|
||||
{
|
||||
printfn('Options:');
|
||||
printfn(' - rebuild');
|
||||
printfn(' Rebuilds search index');
|
||||
@@ -31,19 +34,19 @@ function display_help(): void {
|
||||
/**
|
||||
* Rebuild the search index, creating it if it does not already exist
|
||||
*/
|
||||
function rebuild_index(): void {
|
||||
$db = Data::getConnection();
|
||||
|
||||
function rebuild_index(): void
|
||||
{
|
||||
try {
|
||||
$hasIndex = $db->query("SELECT COUNT(*) FROM sqlite_master WHERE name = 'item_ai'");
|
||||
if ($hasIndex->fetchArray(SQLITE3_NUM)[0] == 0) {
|
||||
$hasIndex = Custom::scalar("SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE name = 'item_ai')", [],
|
||||
new ExistsMapper());
|
||||
if (!$hasIndex) {
|
||||
printfn('Creating search index....');
|
||||
Data::createSearchIndex($db);
|
||||
Data::createSearchIndex();
|
||||
}
|
||||
printfn('Rebuilding search index...');
|
||||
$db->exec("INSERT INTO item_search (item_search) VALUES ('rebuild')");
|
||||
Custom::nonQuery("INSERT INTO item_search (item_search) VALUES ('rebuild')", []);
|
||||
printfn(PHP_EOL . 'Search index rebuilt');
|
||||
} finally {
|
||||
$db->close();
|
||||
} catch (DocumentException $ex) {
|
||||
printfn("$ex");
|
||||
}
|
||||
}
|
||||
|
||||
+49
-57
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
use JetBrains\PhpStorm\NoReturn;
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
use BitBadger\PDODocument\{Count, Custom, Delete, DocumentException, Field, Parameters, Patch, Query};
|
||||
use FeedReaderCentral\{Security, Table, User};
|
||||
|
||||
require __DIR__ . '/../cli-start.php';
|
||||
|
||||
@@ -58,8 +60,8 @@ switch ($argv[1]) {
|
||||
/**
|
||||
* Display the options for this utility and exit
|
||||
*/
|
||||
#[NoReturn]
|
||||
function display_help(): void {
|
||||
function display_help(): never
|
||||
{
|
||||
printfn('Options:');
|
||||
printfn(' - add-user [e-mail] [password]');
|
||||
printfn(' Adds a new user to this instance');
|
||||
@@ -83,24 +85,23 @@ function display_help(): void {
|
||||
/**
|
||||
* Add a new user
|
||||
*/
|
||||
function add_user(): void {
|
||||
function add_user(): void
|
||||
{
|
||||
global $argv;
|
||||
|
||||
$db = Data::getConnection();
|
||||
|
||||
try {
|
||||
// Ensure there is not already a user with this e-mail address
|
||||
$user = Security::findUserByEmail($argv[2], $db);
|
||||
$user = User::findByEmail($argv[2]);
|
||||
if ($user) {
|
||||
printfn('A user with e-mail address "%s" already exists', $argv[2]);
|
||||
return;
|
||||
}
|
||||
|
||||
Security::addUser($argv[2], $argv[3], $db);
|
||||
User::add($argv[2], $argv[3]);
|
||||
|
||||
printfn('User "%s" with password "%s" added successfully', $argv[2], $argv[3]);
|
||||
} finally {
|
||||
$db->close();
|
||||
} catch (DocumentException $ex) {
|
||||
printfn("$ex");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,32 +111,33 @@ function add_user(): void {
|
||||
* @param string $email The e-mail address of the user
|
||||
* @return string The string to use when displaying results
|
||||
*/
|
||||
function display_user(string $email): string {
|
||||
function display_user(string $email): string
|
||||
{
|
||||
return $email == Security::SINGLE_USER_EMAIL ? 'single-user mode user' : "user \"$email\"";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a user's password
|
||||
*/
|
||||
function set_password(string $email, string $password): void {
|
||||
$db = Data::getConnection();
|
||||
function set_password(string $email, string $password): void
|
||||
{
|
||||
try {
|
||||
$displayUser = display_user($email);
|
||||
|
||||
// Ensure this user exists
|
||||
$user = Security::findUserByEmail($email, $db);
|
||||
$user = User::findByEmail($email);
|
||||
if (!$user) {
|
||||
printfn('No %s exists', $displayUser);
|
||||
return;
|
||||
}
|
||||
|
||||
Security::updatePassword($email, $password, $db);
|
||||
Security::updatePassword($email, $password);
|
||||
|
||||
$msg = $email == Security::SINGLE_USER_EMAIL && $password == Security::SINGLE_USER_PASSWORD
|
||||
? 'reset' : sprintf('set to "%s"', $password);
|
||||
printfn('%s password %s successfully', init_cap($displayUser), $msg);
|
||||
} finally {
|
||||
$db->close();
|
||||
} catch (DocumentException $ex) {
|
||||
printfn("$ex");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,76 +146,66 @@ function set_password(string $email, string $password): void {
|
||||
*
|
||||
* @param string $email The e-mail address of the user to be deleted
|
||||
*/
|
||||
function delete_user(string $email): void {
|
||||
|
||||
$db = Data::getConnection();
|
||||
|
||||
function delete_user(string $email): void
|
||||
{
|
||||
try {
|
||||
$displayUser = display_user($email);
|
||||
|
||||
// Get the ID for the provided e-mail address
|
||||
$user = Security::findUserByEmail($email, $db);
|
||||
// Get the user for the provided e-mail address
|
||||
$user = User::findByEmail($email);
|
||||
if (!$user) {
|
||||
printfn('No %s exists', $displayUser);
|
||||
return;
|
||||
}
|
||||
|
||||
$feedCountQuery = $db->prepare('SELECT COUNT(*) FROM feed WHERE user_id = :user');
|
||||
$feedCountQuery->bindValue(':user', $user['id']);
|
||||
$feedCountResult = $feedCountQuery->execute();
|
||||
if (!$feedCountResult) {
|
||||
printfn('SQLite error: %s', $db->lastErrorMsg());
|
||||
try {
|
||||
$feedCount = Count::byFields(Table::FEED, [Field::EQ('user_id', $user->id)]);
|
||||
} catch (DocumentException $ex) {
|
||||
printfn("$ex");
|
||||
return;
|
||||
}
|
||||
$feedCount = $feedCountResult->fetchArray(SQLITE3_NUM);
|
||||
|
||||
$proceed = readline("Delete the $displayUser and their $feedCount[0] feed(s)? (y/N)" . PHP_EOL);
|
||||
$proceed = readline("Delete the $displayUser and their $feedCount feed(s)? (y/N)" . PHP_EOL);
|
||||
if (!$proceed || !str_starts_with(strtolower($proceed), 'y')) {
|
||||
printfn('Deletion canceled');
|
||||
return;
|
||||
}
|
||||
|
||||
$itemDelete = $db->prepare('DELETE FROM item WHERE feed_id IN (SELECT id FROM feed WHERE user_id = :user)');
|
||||
$itemDelete->bindValue(':user', $user['id']);
|
||||
$itemDelete->execute();
|
||||
try {
|
||||
$fields = [Field::EQ('user_id', $user->id, '@user')];
|
||||
Custom::nonQuery(
|
||||
'DELETE FROM ' . Table::ITEM . " WHERE data->>'feed_id' IN (SELECT data->>'id' FROM " . Table::FEED
|
||||
. ' WHERE ' . Query::whereByFields($fields) . ')', Parameters::addFields($fields, []));
|
||||
Delete::byFields(Table::FEED, $fields);
|
||||
Delete::byId(Table::USER, $user->id);
|
||||
|
||||
$feedDelete = $db->prepare('DELETE FROM feed WHERE user_id = :user');
|
||||
$feedDelete->bindValue(':user', $user['id']);
|
||||
$feedDelete->execute();
|
||||
|
||||
$userDelete = $db->prepare('DELETE FROM frc_user WHERE id = :user');
|
||||
$userDelete->bindValue(':user', $user['id']);
|
||||
$userDelete->execute();
|
||||
|
||||
printfn('%s deleted successfully', init_cap($displayUser));
|
||||
} finally {
|
||||
$db->close();
|
||||
printfn('%s deleted successfully', init_cap($displayUser));
|
||||
} catch (DocumentException $ex) {
|
||||
printfn("$ex");
|
||||
}
|
||||
} catch (DocumentException $ex) {
|
||||
printfn("$ex");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the single-user mode user to a different e-mail address and password
|
||||
*/
|
||||
function migrate_single_user(): void {
|
||||
function migrate_single_user(): void
|
||||
{
|
||||
global $argv;
|
||||
|
||||
$db = Data::getConnection();
|
||||
|
||||
try {
|
||||
$single = Security::findUserByEmail(Security::SINGLE_USER_EMAIL, $db);
|
||||
if (!$single) {
|
||||
if (!$single = User::findByEmail(Security::SINGLE_USER_EMAIL)) {
|
||||
printfn('There is no single-user mode user to be migrated');
|
||||
return;
|
||||
}
|
||||
|
||||
$migrateQuery = $db->prepare('UPDATE frc_user SET email = :email, password = :password WHERE id = :id');
|
||||
$migrateQuery->bindValue(':email', $argv[2]);
|
||||
$migrateQuery->bindValue(':password', password_hash($argv[3], Security::PW_ALGORITHM));
|
||||
$migrateQuery->bindValue(':id', $single['id']);
|
||||
$migrateQuery->execute();
|
||||
Patch::byId(Table::USER, $single->id,
|
||||
['email' => $argv[2], 'password' => password_hash($argv[3], Security::PW_ALGORITHM)]);
|
||||
|
||||
printfn('The single user has been moved to "%s", with password "%s"', $argv[2], $argv[3]);
|
||||
} finally {
|
||||
$db->close();
|
||||
} catch (DocumentException $ex) {
|
||||
printfn("$ex");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user