beta4 changes #26

Merged
danieljsummers merged 7 commits from beta5 into main 2024-08-06 23:20:17 +00:00
19 changed files with 72 additions and 228 deletions
Showing only changes of commit f5feef177a - Show all commits

View File

@ -3,14 +3,12 @@
"minimum-stability": "beta",
"require": {
"php": ">=8.2",
"bit-badger/inspired-by-fsharp": "@dev",
"bit-badger/pdo-document": "^1",
"ext-curl": "*",
"ext-dom": "*",
"ext-pdo": "*",
"ext-readline": "*",
"ext-sqlite3": "*",
"graham-campbell/result-type": "^1.1"
"ext-sqlite3": "*"
},
"autoload": {
"psr-4": {

162
src/composer.lock generated
View File

@ -4,24 +4,23 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "2caacda2b3694b265db846cd539e1ca3",
"content-hash": "2966efd32e555ad8b63351673e75b5a5",
"packages": [
{
"name": "bit-badger/inspired-by-fsharp",
"version": "dev-main",
"version": "v1.0.0-beta1",
"source": {
"type": "git",
"url": "https://git.bitbadger.solutions/bit-badger/inspired-by-fsharp",
"reference": "7d25b9ea282c1e244fec6e14a1e1b251314824e4"
"reference": "efb3a4461edcb23e0dd82068adeb0591240870b0"
},
"require": {
"php": "^8"
"php": "^8.2"
},
"require-dev": {
"phpoption/phpoption": "^1",
"phpunit/phpunit": "^11"
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
@ -50,21 +49,21 @@
"rss": "https://git.bitbadger.solutions/bit-badger/inspired-by-fsharp.rss",
"source": "https://git.bitbadger.solutions/bit-badger/inspired-by-fsharp"
},
"time": "2024-07-28T01:20:35+00:00"
"time": "2024-07-28T21:35:11+00:00"
},
{
"name": "bit-badger/pdo-document",
"version": "v1.0.0-beta7",
"version": "v1.0.0-beta8",
"source": {
"type": "git",
"url": "https://git.bitbadger.solutions/bit-badger/pdo-document",
"reference": "57d8f9ddc17169883f7dd77e51dea1443040858b"
"reference": "039283224a173bd3e8a9bc5de0caf349ba7b58e6"
},
"require": {
"bit-badger/inspired-by-fsharp": "^1",
"ext-pdo": "*",
"netresearch/jsonmapper": "^4",
"php": ">=8.2",
"phpoption/phpoption": "^1.9"
"php": ">=8.2"
},
"require-dev": {
"phpunit/phpunit": "^11",
@ -104,69 +103,7 @@
"rss": "https://git.bitbadger.solutions/bit-badger/pdo-document.rss",
"source": "https://git.bitbadger.solutions/bit-badger/pdo-document"
},
"time": "2024-07-25T00:57:23+00:00"
},
{
"name": "graham-campbell/result-type",
"version": "v1.1.3",
"source": {
"type": "git",
"url": "https://github.com/GrahamCampbell/Result-Type.git",
"reference": "3ba905c11371512af9d9bdd27d99b782216b6945"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945",
"reference": "3ba905c11371512af9d9bdd27d99b782216b6945",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0",
"phpoption/phpoption": "^1.9.3"
},
"require-dev": {
"phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
},
"type": "library",
"autoload": {
"psr-4": {
"GrahamCampbell\\ResultType\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
}
],
"description": "An Implementation Of The Result Type",
"keywords": [
"Graham Campbell",
"GrahamCampbell",
"Result Type",
"Result-Type",
"result"
],
"support": {
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
"type": "tidelift"
}
],
"time": "2024-07-20T21:45:45+00:00"
"time": "2024-07-29T00:08:44+00:00"
},
{
"name": "netresearch/jsonmapper",
@ -218,89 +155,12 @@
"source": "https://github.com/cweiske/jsonmapper/tree/v4.4.1"
},
"time": "2024-01-31T06:18:54+00:00"
},
{
"name": "phpoption/phpoption",
"version": "1.9.3",
"source": {
"type": "git",
"url": "https://github.com/schmittjoh/php-option.git",
"reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54",
"reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
},
"branch-alias": {
"dev-master": "1.9-dev"
}
},
"autoload": {
"psr-4": {
"PhpOption\\": "src/PhpOption/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Johannes M. Schmitt",
"email": "schmittjoh@gmail.com",
"homepage": "https://github.com/schmittjoh"
},
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
}
],
"description": "Option Type for PHP",
"keywords": [
"language",
"option",
"php",
"type"
],
"support": {
"issues": "https://github.com/schmittjoh/php-option/issues",
"source": "https://github.com/schmittjoh/php-option/tree/1.9.3"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
"type": "tidelift"
}
],
"time": "2024-07-20T21:41:07+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "beta",
"stability-flags": {
"bit-badger/inspired-by-fsharp": 20
},
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {

View File

@ -104,7 +104,7 @@ class Feed
try {
$tryExisting = Find::firstByFields(Table::Item,
[Field::EQ('feed_id', $feedId), Field::EQ('item_guid', $item->guid)], Item::class);
if ($tryExisting->isDefined()) {
if ($tryExisting->isSome()) {
$existing = $tryExisting->get();
if ($existing->published_on !== $item->publishedOn
|| ($existing->updated_on !== ($item->updatedOn ?? ''))) {
@ -180,16 +180,16 @@ class Feed
public static function refreshFeed(int $feedId, string $url): Result
{
$tryRetrieve = ParsedFeed::retrieve($url);
if (Result::isError($tryRetrieve)) return $tryRetrieve;
if ($tryRetrieve->isError()) return $tryRetrieve;
$feed = $tryRetrieve->getOK();
try {
$feedDoc = Find::byId(Table::Feed, $feedId, self::class);
if ($feedDoc->isEmpty()) return Result::Error('Could not derive date last checked for feed');
if ($feedDoc->isNone()) return Result::Error('Could not derive date last checked for feed');
$lastChecked = date_create_immutable($feedDoc->get()->checked_on ?? WWW_EPOCH);
$itemUpdate = self::updateItems($feedId, $feed, $lastChecked);
if (Result::isError($itemUpdate)) return $itemUpdate;
if ($itemUpdate->isError()) return $itemUpdate;
$patch = [
'title' => $feed->title,
@ -214,7 +214,7 @@ class Feed
public static function add(string $url): Result
{
$tryRetrieve = ParsedFeed::retrieve($url);
if (Result::isError($tryRetrieve)) return $tryRetrieve;
if ($tryRetrieve->isError()) return $tryRetrieve;
$feed = $tryRetrieve->getOK();
try {
@ -226,11 +226,11 @@ class Feed
Document::insert(Table::Feed, self::fromParsed($feed));
$tryDoc = Find::firstByFields(Table::Feed, $fields, self::class);
if ($tryDoc->isEmpty()) return Result::Error('Could not retrieve inserted feed');
if ($tryDoc->isNone()) return Result::Error('Could not retrieve inserted feed');
$doc = $tryDoc->get();
$result = self::updateItems($doc->id, $feed, date_create_immutable(WWW_EPOCH));
return Result::isError($result) ? $result : Result::OK($doc->id);
return $result->isError() ? $result : Result::OK($doc->id);
} catch (DocumentException $ex) {
return Result::Error("$ex");
}
@ -280,8 +280,8 @@ class Feed
try {
self::retrieveAll($_SESSION[Key::UserId])->iter(function (Feed $feed) use (&$errors) {
Result::mapError(function (string $err) use (&$errors) { $errors[] = $err; },
self::refreshFeed($feed->id, $feed->url));
self::refreshFeed($feed->id, $feed->url)
->mapError(function (string $err) use (&$errors) { $errors[] = $err; });
});
} catch (DocumentException $ex) {
return Result::Error("$ex");
@ -299,8 +299,7 @@ class Feed
*/
public static function retrieveById(int $feedId): Option
{
return Option::of(
Find::byId(Table::Feed, $feedId, static::class)
->filter(fn($it) => $it->user_id === $_SESSION[Key::UserId]));
return Find::byId(Table::Feed, $feedId, static::class)
->filter(fn($it) => $it->user_id === $_SESSION[Key::UserId]);
}
}

View File

@ -8,6 +8,7 @@ declare(strict_types=1);
namespace FeedReaderCentral;
use BitBadger\InspiredByFSharp\Option;
use BitBadger\PDODocument\{Configuration, Custom, DocumentException, DocumentList, Field, Parameters, Query};
use BitBadger\PDODocument\Mapper\DocumentMapper;
@ -21,18 +22,8 @@ class ItemList
/** @var DocumentList<ItemWithFeed> The items matching the criteria, lazily iterable */
private DocumentList $dbList;
/** @var string The error message generated by executing a query */
public string $error = '';
/**
* Is there an error condition associated with this list?
*
* @return bool True if there is an error condition associated with this list, false if not
*/
public function isError(): bool
{
return $this->error !== '';
}
/** @var Option<string> The error message generated by executing a query */
private Option $error;
/** @var bool Whether to render a link to the feed to which the item belongs */
public bool $linkFeed = false;
@ -54,6 +45,7 @@ class ItemList
private function __construct(public string $itemType, public string $returnURL = '', array $fields = [],
string $searchWhere = '')
{
$this->error = Option::None();
$allFields = [Data::userIdField(Table::Feed), ...$fields];
try {
$this->dbList = Custom::list(
@ -62,7 +54,7 @@ class ItemList
. "$searchWhere ORDER BY coalesce(item.data->>'updated_on', item.data->>'published_on') DESC",
Parameters::addFields($allFields, []), new DocumentMapper(ItemWithFeed::class));
} catch (DocumentException $ex) {
$this->error = "$ex";
$this->error = Option::Some("$ex");
}
}
@ -152,8 +144,8 @@ class ItemList
*/
public function render(): void
{
if ($this->isError()) {
echo "<p>Error retrieving list:<br>$this->error";
if ($this->error->isSome()) {
echo "<p>Error retrieving list:<br>{$this->error->get()}";
return;
}
$return = $this->returnURL === '' ? '' : '&from=' . urlencode($this->returnURL);

View File

@ -68,8 +68,7 @@ class ItemWithFeed extends Item
public static function retrieveById(int $id): Option
{
$fields = self::idAndUserFields($id);
return Option::of(
Custom::single(self::SELECT_WITH_FEED . ' WHERE ' . Query::whereByFields($fields),
Parameters::addFields($fields, []), new DocumentMapper(self::class)));
return Custom::single(self::SELECT_WITH_FEED . ' WHERE ' . Query::whereByFields($fields),
Parameters::addFields($fields, []), new DocumentMapper(self::class));
}
}

View File

@ -240,7 +240,7 @@ readonly class ParsedFeed
public static function retrieve(string $url): Result
{
$tryDoc = self::retrieveDocument($url);
if (Result::isError($tryDoc)) return $tryDoc;
if ($tryDoc->isError()) return $tryDoc;
$doc = $tryDoc->getOK();
if ($doc['code'] !== 200) {
@ -250,7 +250,7 @@ readonly class ParsedFeed
$start = strtolower(strlen($doc['content']) >= 9 ? substr($doc['content'], 0, 9) : $doc['content']);
if ($start === '<!doctype' || str_starts_with($start, '<html')) {
$derivedURL = self::deriveFeedFromHTML($doc['content']);
if (Result::isError($derivedURL)) return $derivedURL;
if ($derivedURL->isError()) return $derivedURL;
$feedURL = $derivedURL->getOK();
if (!str_starts_with($feedURL, 'http')) {
// Relative URL; feed should be retrieved in the context of the original URL
@ -259,7 +259,7 @@ readonly class ParsedFeed
$feedURL = $original['scheme'] . '://' . $original['host'] . $port . $feedURL;
}
$tryDoc = self::retrieveDocument($feedURL);
if (Result::isError($tryDoc)) return $tryDoc;
if ($tryDoc->isError()) return $tryDoc;
$doc = $tryDoc->getOK();
if ($doc['code'] !== 200) {
return Result::Error("Derived feed URL $url returned HTTP Code {$doc['code']}: {$doc['content']}");
@ -267,7 +267,7 @@ readonly class ParsedFeed
}
$tryParse = self::parseFeed($doc['content']);
if (Result::isError($tryParse)) return $tryParse;
if ($tryParse->isError()) return $tryParse;
$parsed = $tryParse->getOK();
$extract = $parsed->getElementsByTagNameNS(self::ATOM_NS, 'feed')->length > 0

View File

@ -60,10 +60,10 @@ readonly 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 Option<string> $returnTo The URL to which the user should be redirected
* @throws DocumentException if any is encountered
*/
private static function verifyPassword(User $user, string $password, ?string $returnTo): void
private static function verifyPassword(User $user, string $password, Option $returnTo): void
{
if (password_verify($password, $user->password)) {
if (password_needs_rehash($user->password, self::PasswordAlgorithm)) {
@ -71,7 +71,7 @@ readonly class Security
}
$_SESSION[Key::UserId] = $user->id;
$_SESSION[Key::UserEmail] = $user->email;
frc_redirect($returnTo ?? '/');
frc_redirect($returnTo->getOrDefault('/'));
}
}
@ -80,10 +80,10 @@ readonly 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 Option<string> $returnTo The URL to which the user should be redirected
* @throws DocumentException If any is encountered
*/
public static function logOnUser(string $email, string $password, ?string $returnTo): void
public static function logOnUser(string $email, string $password, Option $returnTo): void
{
if (SECURITY_MODEL === self::SingleUserPasswordMode) {
$dbEmail = self::SingleUserEmail;
@ -94,7 +94,7 @@ readonly class Security
}
$dbEmail = $email;
}
Option::iter(fn(User $it) => self::verifyPassword($it, $password, $returnTo), User::findByEmail($dbEmail));
User::findByEmail($dbEmail)->iter(fn(User $it) => self::verifyPassword($it, $password, $returnTo));
add_error('Invalid credentials; log on unsuccessful');
}
@ -118,12 +118,11 @@ readonly class Security
*/
private static function logOnSingleUser(): void
{
$user = User::findByEmail(self::SingleUserEmail);
if (Option::isNone($user)) {
$user = User::findByEmail(self::SingleUserEmail)->getOrCall(function () {
User::add(self::SingleUserEmail, self::SingleUserPassword);
$user = User::findByEmail(self::SingleUserEmail);
}
self::verifyPassword($user->get(), self::SingleUserPassword, $_GET['returnTo']);
return User::findByEmail(self::SingleUserEmail)->get();
});
self::verifyPassword($user, self::SingleUserPassword, $_GET['returnTo']);
}
/**

View File

@ -35,7 +35,7 @@ class User
*/
public static function findByEmail(string $email): Option
{
return Option::of(Find::firstByFields(Table::User, [Field::EQ('email', $email)], User::class));
return Find::firstByFields(Table::User, [Field::EQ('email', $email)], User::class);
}
/**

View File

@ -19,20 +19,20 @@ include '../start.php';
FeedReaderCentral\Security::verifyUser();
$id = key_exists('id', $_GET) ? (int)$_GET['id'] : -1;
$item = Option::getOrCall(not_found(...), ItemWithFeed::retrieveById($id));
$item = ItemWithFeed::retrieveById($id)->getOrCall(not_found(...));
if (key_exists('action', $_GET)) {
Option::iter(function (int $flag) use ($id, &$item) {
(match ($_GET['action']) {
'add' => Option::Some(1),
'remove' => Option::Some(0),
default => Option::None(),
})->iter(function (int $flag) use ($id, &$item) {
try {
Patch::byId(Table::Item, $id, ['is_bookmarked' => $flag]);
$item->is_bookmarked = $flag;
} catch (DocumentException $ex) {
add_error("$ex");
}
}, match ($_GET['action']) {
'add' => Option::Some(1),
'remove' => Option::Some(0),
default => Option::None(),
});
}

View File

@ -10,7 +10,7 @@
declare(strict_types=1);
use BitBadger\InspiredByFSharp\{Option, Result};
use BitBadger\InspiredByFSharp\Result;
use BitBadger\PDODocument\{Delete, DocumentException, Field};
use FeedReaderCentral\{Feed, Security, Table};
@ -23,7 +23,7 @@ $feedId = key_exists('id', $_GET) ? (int)$_GET['id'] : -1;
switch ($_SERVER['REQUEST_METHOD']) {
case 'DELETE':
try {
$feed = Option::getOrCall(not_found(...), Feed::retrieveById($feedId));
$feed = Feed::retrieveById($feedId)->getOrCall(not_found(...));
Delete::byFields(Table::Item, [Field::EQ('feed_id', $feed->id)]);
Delete::byId(Table::Feed, $feed->id);
add_info('Feed &ldquo;' . htmlentities($feed->title) . '&rdquo; deleted successfully');
@ -40,11 +40,11 @@ switch ($_SERVER['REQUEST_METHOD']) {
} else {
$feedId = (int)$_POST['id'];
$toEdit = Feed::retrieveById($feedId);
$result = Option::isSome($toEdit)
$result = $toEdit->isSome()
? Feed::update($toEdit->get(), $_POST['url'])
: Result::Error("Feed $feedId not found");
}
if (Result::isOK($result)) {
if ($result->isOK()) {
add_info('Feed saved successfully');
frc_redirect('/feeds');
}
@ -63,7 +63,7 @@ if ($feedId == -1) {
$title = 'Edit RSS Feed';
$feed = $feedId == 'error'
? new Feed(id: (int)$_POST['id'], url: $_POST['url'] ?? '')
: Option::getOrCall(not_found(...), Feed::retrieveById((int)$feedId));
: Feed::retrieveById((int)$feedId)->getOrCall(not_found(...));
}
page_head($title); ?>

View File

@ -10,7 +10,6 @@
declare(strict_types=1);
use BitBadger\InspiredByFSharp\Option;
use FeedReaderCentral\{Feed, ItemList};
include '../../start.php';
@ -18,7 +17,7 @@ include '../../start.php';
FeedReaderCentral\Security::verifyUser();
$id = key_exists('id', $_GET) ? (int)$_GET['id'] : -1;
$feed = Option::getOrCall(not_found(...), Feed::retrieveById($id));
$feed = Feed::retrieveById($id)->getOrCall(not_found(...));
$list = match (true) {
key_exists('unread', $_GET) => ItemList::unreadForFeed($feed->id),

View File

@ -30,7 +30,7 @@ $feeds->iter(function (Feed $feed) {
SELECT (SELECT COUNT(*) FROM $item WHERE data->>'feed_id' = :feed) AS total,
(SELECT COUNT(*) FROM $item WHERE data->>'feed_id' = :feed AND data->>'is_read' = 0) AS unread,
(SELECT COUNT(*) FROM $item WHERE data->>'feed_id' = :feed AND data->>'is_bookmarked' = 1) AS marked
SQL, [':feed' => $feed->id], new ArrayMapper())->getOrElse(['total' => 0, 'unread' => 0, 'marked' => 0]);
SQL, [':feed' => $feed->id], new ArrayMapper())->getOrDefault(['total' => 0, 'unread' => 0, 'marked' => 0]);
echo '<p><strong>' . htmlentities($feed->title) . '</strong><br>'
. '<span class=meta><em>Last Updated ' . date_time($feed->updated_on) . ' &bull; '
. 'As of ' . date_time($feed->checked_on) . '</em><br>' . hx_get("/feed/?id=$feed->id", 'Edit') . ' &bull; '

View File

@ -10,7 +10,6 @@
declare(strict_types=1);
use BitBadger\InspiredByFSharp\Result;
use FeedReaderCentral\{Feed, ItemList};
include '../start.php';
@ -19,7 +18,7 @@ FeedReaderCentral\Security::verifyUser();
if (key_exists('refresh', $_GET)) {
$refreshResult = Feed::refreshAll();
if (Result::isOK($refreshResult)) {
if ($refreshResult->isOK()) {
add_info('All feeds refreshed successfully');
} else {
add_error(nl2br($refreshResult->getError()));

View File

@ -10,7 +10,6 @@
declare(strict_types=1);
use BitBadger\InspiredByFSharp\Option;
use BitBadger\PDODocument\{Delete, DocumentException, Patch};
use FeedReaderCentral\{ItemWithFeed, Table};
@ -50,7 +49,7 @@ switch ($_SERVER['REQUEST_METHOD']) {
frc_redirect($from);
}
!$item = Option::getOrCall(not_found(...), ItemWithFeed::retrieveById($id));
$item = ItemWithFeed::retrieveById($id)->getOrCall(not_found(...));
try {
Patch::byId(Table::Item, $id, ['is_read' => 1]);
} catch (DocumentException $ex) {

View File

@ -13,6 +13,7 @@ declare(strict_types=1);
include '../../start.php';
use BitBadger\InspiredByFSharp\Option;
use FeedReaderCentral\{Key, Security};
Security::verifyUser(redirectIfAnonymous: false);
@ -21,7 +22,7 @@ Security::verifyUser(redirectIfAnonymous: false);
if (key_exists(Key::UserId, $_SESSION)) frc_redirect('/');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
Security::logOnUser($_POST['email'] ?? '', $_POST['password'], $_POST['returnTo'] ?? null);
Security::logOnUser($_POST['email'] ?? '', $_POST['password'], Option::of($_POST['returnTo'] ?? null));
// If we're still here, something didn't work; preserve the returnTo parameter
$_GET['returnTo'] = $_POST['returnTo'];
}

View File

@ -10,7 +10,6 @@
declare(strict_types=1);
use BitBadger\InspiredByFSharp\Option;
use BitBadger\PDODocument\Configuration;
use FeedReaderCentral\{Key, Security, User};

View File

@ -52,7 +52,7 @@ function json_column_exists(): bool
{
try {
$table = Custom::single("SELECT sql FROM sqlite_master WHERE tbl_name='frc_user'", [], new ArrayMapper())
->getOrElse(['sql' => '']);
->getOrDefault(['sql' => '']);
return $table && substr_compare(strtolower($table['sql']), 'data text not null', 0) >= 0;
} catch (DocumentException $ex) {
printfn("ERR $ex");

View File

@ -50,9 +50,9 @@ function refresh_all(): void
$userKey = "$feed->user_id";
if (!key_exists($userKey, $users)) {
$users[$userKey] = Find::byId(Table::User, $feed->user_id, User::class)
->getOrElse(new User(email: 'user-not-found'));
->getOrDefault(new User(email: 'user-not-found'));
}
if (Result::isError($result)) {
if ($result->isError()) {
printfn('ERR (%s) %s', $users[$userKey]->email, $feed->url);
printfn(' %s', $result->getError());
} else {

View File

@ -104,7 +104,7 @@ function add_user(): void
try {
// Ensure there is not already a user with this e-mail address
$user = User::findByEmail($argv[2]);
if (Option::isSome($user)) {
if ($user->isSome()) {
printfn('A user with e-mail address "%s" already exists', $argv[2]);
return;
}
@ -138,7 +138,7 @@ function set_password(string $email, string $password): void
// Ensure this user exists
$user = User::findByEmail($email);
if (Option::isNone($user)) {
if ($user->isNone()) {
printfn('No %s exists', $displayUser);
return;
}
@ -165,7 +165,7 @@ function delete_user(string $email): void
// Get the user for the provided e-mail address
$tryUser = User::findByEmail($email);
if (Option::isNone($tryUser)) {
if ($tryUser->isNone()) {
printfn('No %s exists', $displayUser);
return;
}
@ -210,7 +210,7 @@ function migrate_single_user(): void
try {
$single = User::findByEmail(Security::SingleUserEmail);
if (Option::isNone($single)) {
if ($single->isNone()) {
printfn('There is no single-user mode user to be migrated');
return;
}