Add table to doc util script
- Remove db parameter from several places - Add constructors for document types
This commit is contained in:
@@ -14,18 +14,8 @@ use SQLite3;
|
||||
/**
|
||||
* A centralized place for data access for the application
|
||||
*/
|
||||
class Data {
|
||||
|
||||
/**
|
||||
* Obtain a new connection to the database
|
||||
* @return SQLite3 A new connection to the database
|
||||
*/
|
||||
public static function getConnection(): SQLite3 {
|
||||
$db = new SQLite3(implode(DIRECTORY_SEPARATOR, [__DIR__, '..', 'data', DATABASE_NAME]));
|
||||
$db->exec('PRAGMA foreign_keys = ON;');
|
||||
return $db;
|
||||
}
|
||||
|
||||
class Data
|
||||
{
|
||||
/**
|
||||
* Create the search index and synchronization triggers for the item table
|
||||
*
|
||||
@@ -71,51 +61,17 @@ class Data {
|
||||
{
|
||||
$tables = Custom::array("SELECT name FROM sqlite_master WHERE type = 'table'", [], new StringMapper('name'));
|
||||
$db = Configuration::dbConn();
|
||||
// $tables = array();
|
||||
// $tableQuery = $db->query("SELECT name FROM sqlite_master WHERE type = 'table'");
|
||||
// while ($table = $tableQuery->fetchArray(SQLITE3_NUM)) $tables[] = $table[0];
|
||||
if (!in_array(Table::USER, $tables)) {
|
||||
Definition::ensureTable(Table::USER, $db);
|
||||
Definition::ensureFieldIndex(Table::USER, 'email', ['email'], $db);
|
||||
// $db->exec(<<<'SQL'
|
||||
// CREATE TABLE frc_user (
|
||||
// id INTEGER NOT NULL PRIMARY KEY,
|
||||
// email TEXT NOT NULL,
|
||||
// password TEXT NOT NULL)
|
||||
// SQL);
|
||||
// $db->exec('CREATE INDEX idx_user_email ON frc_user (email)');
|
||||
}
|
||||
if (!in_array(Table::FEED, $tables)) {
|
||||
Definition::ensureTable(Table::FEED, $db);
|
||||
Definition::ensureFieldIndex(Table::FEED, 'user', ['user_id'], $db);
|
||||
// $db->exec(<<<'SQL'
|
||||
// CREATE TABLE feed (
|
||||
// id INTEGER NOT NULL PRIMARY KEY,
|
||||
// user_id INTEGER NOT NULL,
|
||||
// url TEXT NOT NULL,
|
||||
// title TEXT,
|
||||
// updated_on TEXT,
|
||||
// checked_on TEXT,
|
||||
// FOREIGN KEY (user_id) REFERENCES frc_user (id))
|
||||
// SQL);
|
||||
}
|
||||
if (!in_array(Table::ITEM, $tables)) {
|
||||
Definition::ensureTable(Table::ITEM, $db);
|
||||
Definition::ensureFieldIndex(Table::ITEM, 'feed', ['feed_id', 'item_link'], $db);
|
||||
// $db->exec(<<<'SQL'
|
||||
// CREATE TABLE item (
|
||||
// id INTEGER NOT NULL PRIMARY KEY,
|
||||
// feed_id INTEGER NOT NULL,
|
||||
// title TEXT NOT NULL,
|
||||
// item_guid TEXT NOT NULL,
|
||||
// item_link TEXT NOT NULL,
|
||||
// published_on TEXT NOT NULL,
|
||||
// updated_on TEXT,
|
||||
// content TEXT NOT NULL,
|
||||
// is_read BOOLEAN NOT NULL DEFAULT 0,
|
||||
// is_bookmarked BOOLEAN NOT NULL DEFAULT 0,
|
||||
// FOREIGN KEY (feed_id) REFERENCES feed (id))
|
||||
// SQL);
|
||||
self::createSearchIndex($db);
|
||||
}
|
||||
$db->close();
|
||||
@@ -127,21 +83,12 @@ class Data {
|
||||
* @param ?string $value The date/time to be parsed and formatted
|
||||
* @return string|null The date/time in `DateTimeInterface::ATOM` format, or `null` if the input cannot be parsed
|
||||
*/
|
||||
public static function formatDate(?string $value): ?string {
|
||||
public static function formatDate(?string $value): ?string
|
||||
{
|
||||
try {
|
||||
return $value ? (new DateTimeImmutable($value))->format(DateTimeInterface::ATOM) : null;
|
||||
} catch (Exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last SQLite error message as a result array
|
||||
*
|
||||
* @param SQLite3 $db The database connection on which the error has occurred
|
||||
* @return string[] ['error' => message] for last SQLite error message
|
||||
*/
|
||||
public static function error(SQLite3 $db): array {
|
||||
return ['error' => 'SQLite error: ' . $db->lastErrorMsg()];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,25 +33,19 @@ class Feed
|
||||
/** @var int Purge items in number greater than the specified number of items to keep */
|
||||
public const int PURGE_BY_COUNT = 3;
|
||||
|
||||
// ***** PROPERTIES *****
|
||||
|
||||
/** @var int The ID of the feed */
|
||||
public int $id = 0;
|
||||
|
||||
/** @var int The ID of the user to whom this subscription belongs */
|
||||
public int $user_id = 0;
|
||||
|
||||
/** @var string The URL of the feed */
|
||||
public string $url = '';
|
||||
|
||||
/** @var string|null The title of this feed */
|
||||
public ?string $title = null;
|
||||
|
||||
/** @var string|null The date/time items in this feed were last updated */
|
||||
public ?string $updated_on = null;
|
||||
|
||||
/** @var string|null The date/time this feed was last checked */
|
||||
public ?string $checked_on = null;
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param int $id The ID of the feed
|
||||
* @param int $user_id The ID of the user to whom this subscription belongs
|
||||
* @param string $url The URL of the feed
|
||||
* @param string|null $title The title of this feed
|
||||
* @param string|null $updated_on The date/time items in this feed were last updated
|
||||
* @param string|null $checked_on The date/time this feed was last checked
|
||||
*/
|
||||
public function __construct(public int $id = 0, public int $user_id = 0, public string $url = '',
|
||||
public ?string $title = null, public ?string $updated_on = null,
|
||||
public ?string $checked_on = null) { }
|
||||
|
||||
// ***** STATIC FUNCTIONS *****
|
||||
|
||||
|
||||
@@ -14,35 +14,24 @@ use BitBadger\Documents\SQLite\Parameters;
|
||||
*/
|
||||
class Item
|
||||
{
|
||||
/** @var int The ID of this item in the Feed Reader Central database */
|
||||
public int $id = 0;
|
||||
|
||||
/** @var int The ID of the feed to which this item belongs */
|
||||
public int $feed_id = 0;
|
||||
|
||||
/** @var string The title of this item */
|
||||
public string $title = '';
|
||||
|
||||
/** @var string The Globally Unique ID (GUID) for this item (an attribute in the feed XML) */
|
||||
public string $item_guid = '';
|
||||
|
||||
/** @var string The link to the item on its original site */
|
||||
public string $item_link = '';
|
||||
|
||||
/** @var string The date/time this item was published */
|
||||
public string $published_on = '';
|
||||
|
||||
/** @var string|null The date/time this item was last updated */
|
||||
public ?string $updated_on = null;
|
||||
|
||||
/** @var string The content for this item */
|
||||
public string $content = '';
|
||||
|
||||
/** @var int 1 if the item has been read, 0 if not */
|
||||
public int $is_read = 0;
|
||||
|
||||
/** @var int 1 if the item is bookmarked, 0 if not */
|
||||
public int $is_bookmarked = 0;
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param int $id The ID of this item in the Feed Reader Central database
|
||||
* @param int $feed_id The ID of the feed to which this item belongs
|
||||
* @param string $title The title of this item
|
||||
* @param string $item_guid The Globally Unique ID (GUID) for this item (an attribute in the feed XML)
|
||||
* @param string $item_link The link to the item on its original site
|
||||
* @param string $published_on The date/time this item was published
|
||||
* @param string|null $updated_on The date/time this item was last updated
|
||||
* @param string $content The content for this item
|
||||
* @param int $is_read 1 if the item has been read, 0 if not
|
||||
* @param int $is_bookmarked 1 if the item is bookmarked, 0 if not
|
||||
*/
|
||||
public function __construct(public int $id = 0, public int $feed_id = 0, public string $title = '',
|
||||
public string $item_guid = '', public string $item_link = '',
|
||||
public string $published_on = '', public ?string $updated_on = null,
|
||||
public string $content = '', public int $is_read = 0, public int $is_bookmarked = 0) { }
|
||||
|
||||
/**
|
||||
* Has the item been read?
|
||||
@@ -73,16 +62,14 @@ class Item
|
||||
*/
|
||||
public static function fromFeedItem(int $feedId, ParsedItem $item): static
|
||||
{
|
||||
$it = new static();
|
||||
$it->feed_id = $feedId;
|
||||
$it->item_guid = $item->guid;
|
||||
$it->item_link = $item->link;
|
||||
$it->title = $item->title;
|
||||
$it->published_on = $item->publishedOn;
|
||||
$it->updated_on = $item->updatedOn;
|
||||
$it->content = $item->content;
|
||||
|
||||
return $it;
|
||||
return new static(
|
||||
feed_id: $feedId,
|
||||
title: $item->title,
|
||||
item_guid: $item->guid,
|
||||
item_link: $item->link,
|
||||
published_on: $item->publishedOn,
|
||||
updated_on: $item->updatedOn,
|
||||
content: $item->content);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,7 +48,7 @@ class ItemList {
|
||||
{
|
||||
$result = $query->execute();
|
||||
if (!$result) {
|
||||
$this->error = Data::error($db)['error'];
|
||||
$this->error = 'SQLite error: ' . $db->lastErrorMsg();
|
||||
} else {
|
||||
$this->items = $result;
|
||||
}
|
||||
|
||||
@@ -4,13 +4,12 @@ namespace FeedReaderCentral;
|
||||
use BitBadger\Documents\DocumentException;
|
||||
use BitBadger\Documents\Field;
|
||||
use BitBadger\Documents\SQLite\Patch;
|
||||
use SQLite3;
|
||||
|
||||
/**
|
||||
* Security functions
|
||||
*/
|
||||
class Security {
|
||||
|
||||
class Security
|
||||
{
|
||||
/** @var int Run as a single user requiring no password */
|
||||
public const int SINGLE_USER = 0;
|
||||
|
||||
@@ -35,17 +34,16 @@ class Security {
|
||||
* @param User $user The user information retrieved from the database
|
||||
* @param string $password The password provided by the user
|
||||
* @param string|null $returnTo The URL to which the user should be redirected
|
||||
* @param SQLite3 $db The database connection to use to verify the user's credentials
|
||||
* @throws DocumentException if any is encountered
|
||||
*/
|
||||
private static function verifyPassword(User $user, string $password, ?string $returnTo, SQLite3 $db): void
|
||||
private static function verifyPassword(User $user, string $password, ?string $returnTo): void
|
||||
{
|
||||
if (password_verify($password, $user->password)) {
|
||||
if (password_needs_rehash($user->password, self::PW_ALGORITHM)) {
|
||||
Patch::byId(Table::USER, $user->id, ['password' => password_hash($password, self::PW_ALGORITHM)], $db);
|
||||
Patch::byId(Table::USER, $user->id, ['password' => password_hash($password, self::PW_ALGORITHM)]);
|
||||
}
|
||||
$_SESSION[Key::USER_ID] = $user['id'];
|
||||
$_SESSION[Key::USER_EMAIL] = $user['email'];
|
||||
$_SESSION[Key::USER_ID] = $user->id;
|
||||
$_SESSION[Key::USER_EMAIL] = $user->email;
|
||||
frc_redirect($returnTo ?? '/');
|
||||
}
|
||||
}
|
||||
@@ -56,10 +54,9 @@ class Security {
|
||||
* @param string $email The e-mail address for the user (cannot be the single-user mode user)
|
||||
* @param string $password The password provided by the user
|
||||
* @param string|null $returnTo The URL to which the user should be redirected
|
||||
* @param SQLite3 $db The database connection to use to verify the user's credentials
|
||||
* @throws DocumentException If any is encountered
|
||||
*/
|
||||
public static function logOnUser(string $email, string $password, ?string $returnTo, SQLite3 $db): void {
|
||||
public static function logOnUser(string $email, string $password, ?string $returnTo): void {
|
||||
if (SECURITY_MODEL == self::SINGLE_USER_WITH_PASSWORD) {
|
||||
$dbEmail = self::SINGLE_USER_EMAIL;
|
||||
} else {
|
||||
@@ -70,7 +67,7 @@ class Security {
|
||||
$dbEmail = $email;
|
||||
}
|
||||
$user = User::findByEmail($dbEmail);
|
||||
if ($user) self::verifyPassword($user, $password, $returnTo, $db);
|
||||
if ($user) self::verifyPassword($user, $password, $returnTo);
|
||||
add_error('Invalid credentials; log on unsuccessful');
|
||||
}
|
||||
|
||||
@@ -79,40 +76,37 @@ class Security {
|
||||
*
|
||||
* @param string $email The e-mail address of the user whose password should be updated
|
||||
* @param string $password The new password for this user
|
||||
* @param SQLite3 $db The database connection to use in updating the password
|
||||
* @throws DocumentException If any is encountered
|
||||
*/
|
||||
public static function updatePassword(string $email, string $password, SQLite3 $db): void {
|
||||
public static function updatePassword(string $email, string $password): void {
|
||||
Patch::byFields(Table::USER, [Field::EQ('email', $email)],
|
||||
['password' => password_hash($password, self::PW_ALGORITHM)], $db);
|
||||
['password' => password_hash($password, self::PW_ALGORITHM)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log on the single user
|
||||
*
|
||||
* @param SQLite3 $db The data connection to use to retrieve the user
|
||||
* @throws DocumentException If any is encountered
|
||||
*/
|
||||
private static function logOnSingleUser(SQLite3 $db): void {
|
||||
private static function logOnSingleUser(): void {
|
||||
$user = User::findByEmail(self::SINGLE_USER_EMAIL);
|
||||
if (!$user) {
|
||||
User::add(self::SINGLE_USER_EMAIL, self::SINGLE_USER_PASSWORD, $db);
|
||||
User::add(self::SINGLE_USER_EMAIL, self::SINGLE_USER_PASSWORD);
|
||||
$user = User::findByEmail(self::SINGLE_USER_EMAIL);
|
||||
}
|
||||
self::verifyPassword($user, self::SINGLE_USER_PASSWORD, $_GET['returnTo'], $db);
|
||||
self::verifyPassword($user, self::SINGLE_USER_PASSWORD, $_GET['returnTo']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that user is logged on
|
||||
*
|
||||
* @param SQLite3 $db The data connection to use if required
|
||||
* @param bool $redirectIfAnonymous Whether to redirect the request if there is no user logged on
|
||||
* @throws DocumentException If any is encountered
|
||||
*/
|
||||
public static function verifyUser(SQLite3 $db, bool $redirectIfAnonymous = true): void {
|
||||
public static function verifyUser(bool $redirectIfAnonymous = true): void {
|
||||
if (key_exists(Key::USER_ID, $_SESSION)) return;
|
||||
|
||||
if (SECURITY_MODEL == self::SINGLE_USER) self::logOnSingleUser($db);
|
||||
if (SECURITY_MODEL == self::SINGLE_USER) self::logOnSingleUser();
|
||||
|
||||
if (SECURITY_MODEL != self::SINGLE_USER_WITH_PASSWORD && SECURITY_MODEL != self::MULTI_USER) {
|
||||
die('Unrecognized security model (' . SECURITY_MODEL . ')');
|
||||
|
||||
@@ -3,8 +3,12 @@ namespace FeedReaderCentral;
|
||||
|
||||
use BitBadger\Documents\DocumentException;
|
||||
use BitBadger\Documents\Field;
|
||||
use BitBadger\Documents\Query;
|
||||
use BitBadger\Documents\SQLite\Custom;
|
||||
use BitBadger\Documents\SQLite\Document;
|
||||
use BitBadger\Documents\SQLite\Find;
|
||||
use BitBadger\Documents\SQLite\Parameters;
|
||||
use BitBadger\Documents\SQLite\Results;
|
||||
use SQLite3;
|
||||
|
||||
/**
|
||||
@@ -12,14 +16,14 @@ use SQLite3;
|
||||
*/
|
||||
class User
|
||||
{
|
||||
/** @var int The ID of the user */
|
||||
public int $id = 0;
|
||||
|
||||
/** @var string The e-mail address for the user */
|
||||
public string $email = '';
|
||||
|
||||
/** @var string The password for the user */
|
||||
public string $password = '';
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param int $id The ID of the user
|
||||
* @param string $email The e-mail address for the user
|
||||
* @param string $password The password for the user
|
||||
*/
|
||||
public function __construct(public int $id = 0, public string $email = '', public string $password = '') { }
|
||||
|
||||
/**
|
||||
* Find a user by their e=mail address
|
||||
@@ -38,15 +42,34 @@ class User
|
||||
*
|
||||
* @param string $email The e-mail address for the user
|
||||
* @param string $password The user's password
|
||||
* @param SQLite3 $db The data connection to use to add the user
|
||||
* @throws DocumentException If any is encountered
|
||||
*/
|
||||
public static function add(string $email, string $password, SQLite3 $db): void
|
||||
public static function add(string $email, string $password): void
|
||||
{
|
||||
$user = new User();
|
||||
$user->email = $email;
|
||||
$user->password = $password;
|
||||
Document::insert(Table::USER, $user, $db);
|
||||
Document::insert(Table::USER, new User(email: $email, password: $password));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this user have any bookmarked items?
|
||||
* @return bool True if the user has any bookmarked items, false if not
|
||||
* @throws DocumentException If any is encountered
|
||||
*/
|
||||
public static function hasBookmarks(): bool
|
||||
{
|
||||
$userField = Field::EQ('user_id', $_SESSION[Key::USER_ID], '@user');
|
||||
$userField->qualifier = Table::FEED;
|
||||
$bookField = Field::EQ('is_bookmarked', 1, '@book');
|
||||
$bookField->qualifier = Table::ITEM;
|
||||
$fields = [$userField, $bookField];
|
||||
|
||||
$item = Table::ITEM;
|
||||
$feed = Table::FEED;
|
||||
$where = Query::whereByFields($fields);
|
||||
return Custom::scalar(<<<SQL
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM $item INNER JOIN $feed ON $item.data->>'feed_id' = $feed.data->>'id'
|
||||
WHERE $where)
|
||||
SQL, Parameters::addFields($fields, []), Results::toExists(...));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user