From 52ec3f819c0e88453a7749c3ff5f697a3c32fa02 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sat, 7 Dec 2024 22:30:03 -0500 Subject: [PATCH] WIP on option / PJson integration --- src/composer.json | 3 +- src/composer.lock | 238 +++++++++++------- src/lib/Domain/History.php | 38 ++- src/lib/Domain/Note.php | 5 +- src/lib/Domain/Recurrence.php | 46 +++- src/lib/Domain/Request.php | 137 +++++----- src/lib/UI/Component.php | 30 ++- src/public/request/cancel-snooze.php | 3 +- src/public/request/edit.php | 14 +- src/public/request/full.php | 8 +- src/public/request/save.php | 16 +- .../user/{log-on.php => log-on/index.php} | 2 +- src/start.php | 2 +- 13 files changed, 316 insertions(+), 226 deletions(-) rename src/public/user/{log-on.php => log-on/index.php} (81%) diff --git a/src/composer.json b/src/composer.json index 2a09ca0..eddd6c8 100644 --- a/src/composer.json +++ b/src/composer.json @@ -4,11 +4,12 @@ "php": ">=8.4", "ext-pdo": "*", "ext-sqlite3": "*", + "auth0/auth0-php": "^8.11", "bit-badger/pdo-document": "^2", "guzzlehttp/guzzle": "^7.8", "guzzlehttp/psr7": "^2.6", "http-interop/http-factory-guzzle": "^1.2", - "auth0/auth0-php": "^8.11", + "square/pjson": "^0.5", "vlucas/phpdotenv": "^5.6" }, "autoload": { diff --git a/src/composer.lock b/src/composer.lock index 1838962..07511eb 100644 --- a/src/composer.lock +++ b/src/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bc1634b2de4b7620e11b44a3ab432290", + "content-hash": "b84c4d74a4f4681747b73de459e232c7", "packages": [ { "name": "auth0/auth0-php", @@ -994,40 +994,40 @@ }, { "name": "psr-discovery/all", - "version": "1.0.1", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/psr-discovery/all.git", - "reference": "e353ca0cac46b2e954f4a3ee3a13f0de8be7b87b" + "reference": "840bc0cde7d56e9296606f7a6f0cfc8301fda57d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/psr-discovery/all/zipball/e353ca0cac46b2e954f4a3ee3a13f0de8be7b87b", - "reference": "e353ca0cac46b2e954f4a3ee3a13f0de8be7b87b", + "url": "https://api.github.com/repos/psr-discovery/all/zipball/840bc0cde7d56e9296606f7a6f0cfc8301fda57d", + "reference": "840bc0cde7d56e9296606f7a6f0cfc8301fda57d", "shasum": "" }, "require": { - "php": "^8.1", - "psr-discovery/cache-implementations": "^1.0", - "psr-discovery/container-implementations": "^1.0", - "psr-discovery/event-dispatcher-implementations": "^1.0", - "psr-discovery/http-client-implementations": "^1.0", - "psr-discovery/http-factory-implementations": "^1.0", - "psr-discovery/log-implementations": "^1.0" + "php": "^8.2", + "psr-discovery/cache-implementations": "^1", + "psr-discovery/container-implementations": "^1", + "psr-discovery/event-dispatcher-implementations": "^1", + "psr-discovery/http-client-implementations": "^1", + "psr-discovery/http-factory-implementations": "^1", + "psr-discovery/log-implementations": "^1" }, "type": "metapackage", "extra": { "merge-plugin": { - "ignore-duplicates": false, "include": [ "composer.local.json" ], + "recurse": true, + "replace": true, "merge-dev": true, "merge-extra": false, - "merge-extra-deep": false, "merge-scripts": false, - "recurse": true, - "replace": true + "merge-extra-deep": false, + "ignore-duplicates": false } }, "autoload": { @@ -1059,26 +1059,26 @@ "psr-6" ], "support": { - "source": "https://github.com/psr-discovery/all/tree/1.0.1" + "source": "https://github.com/psr-discovery/all/tree/1.2.0" }, - "time": "2024-03-04T21:20:17+00:00" + "time": "2024-12-05T17:59:32+00:00" }, { "name": "psr-discovery/cache-implementations", - "version": "1.1.1", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/psr-discovery/cache-implementations.git", - "reference": "ebede0af34a7fd3c5564809e659ee69c0ab85ff6" + "reference": "ba247db9da1289b5880bf1b28c4280c16370ea3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/psr-discovery/cache-implementations/zipball/ebede0af34a7fd3c5564809e659ee69c0ab85ff6", - "reference": "ebede0af34a7fd3c5564809e659ee69c0ab85ff6", + "url": "https://api.github.com/repos/psr-discovery/cache-implementations/zipball/ba247db9da1289b5880bf1b28c4280c16370ea3e", + "reference": "ba247db9da1289b5880bf1b28c4280c16370ea3e", "shasum": "" }, "require": { - "php": "^8.1", + "php": "^8.2", "psr-discovery/discovery": "^1.0", "psr/cache": "^1.0 | ^2.0 | ^3.0" }, @@ -1095,16 +1095,16 @@ "type": "library", "extra": { "merge-plugin": { - "ignore-duplicates": false, "include": [ "composer.local.json" ], + "recurse": true, + "replace": true, "merge-dev": true, "merge-extra": false, - "merge-extra-deep": false, "merge-scripts": false, - "recurse": true, - "replace": true + "merge-extra-deep": false, + "ignore-duplicates": false } }, "autoload": { @@ -1134,26 +1134,26 @@ ], "support": { "issues": "https://github.com/psr-discovery/cache-implementations/issues", - "source": "https://github.com/psr-discovery/cache-implementations/tree/1.1.1" + "source": "https://github.com/psr-discovery/cache-implementations/tree/1.2.0" }, - "time": "2024-03-04T21:22:36+00:00" + "time": "2024-12-05T17:55:07+00:00" }, { "name": "psr-discovery/container-implementations", - "version": "1.1.1", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/psr-discovery/container-implementations.git", - "reference": "728a452b32b0bb60c4bac43b18db2e3105bb8d7e" + "reference": "451bb93b473f194a2984c3e7ae0b162e44c55ba1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/psr-discovery/container-implementations/zipball/728a452b32b0bb60c4bac43b18db2e3105bb8d7e", - "reference": "728a452b32b0bb60c4bac43b18db2e3105bb8d7e", + "url": "https://api.github.com/repos/psr-discovery/container-implementations/zipball/451bb93b473f194a2984c3e7ae0b162e44c55ba1", + "reference": "451bb93b473f194a2984c3e7ae0b162e44c55ba1", "shasum": "" }, "require": { - "php": "^8.1", + "php": "^8.2", "psr-discovery/discovery": "^1.0", "psr/container": "^1.0 | ^2.0" }, @@ -1170,16 +1170,16 @@ "type": "library", "extra": { "merge-plugin": { - "ignore-duplicates": false, "include": [ "composer.local.json" ], + "recurse": true, + "replace": true, "merge-dev": true, "merge-extra": false, - "merge-extra-deep": false, "merge-scripts": false, - "recurse": true, - "replace": true + "merge-extra-deep": false, + "ignore-duplicates": false } }, "autoload": { @@ -1207,27 +1207,27 @@ ], "support": { "issues": "https://github.com/psr-discovery/container-implementations/issues", - "source": "https://github.com/psr-discovery/container-implementations/tree/1.1.1" + "source": "https://github.com/psr-discovery/container-implementations/tree/1.2.0" }, - "time": "2024-03-04T21:24:05+00:00" + "time": "2024-12-05T17:42:15+00:00" }, { "name": "psr-discovery/discovery", - "version": "1.1.2", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/psr-discovery/discovery.git", - "reference": "f94a41c150efaffd6f4c23ef95e31cae7a83704f" + "reference": "636f67406eadd33a66a7e65b9f0e26abfd7614ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/psr-discovery/discovery/zipball/f94a41c150efaffd6f4c23ef95e31cae7a83704f", - "reference": "f94a41c150efaffd6f4c23ef95e31cae7a83704f", + "url": "https://api.github.com/repos/psr-discovery/discovery/zipball/636f67406eadd33a66a7e65b9f0e26abfd7614ac", + "reference": "636f67406eadd33a66a7e65b9f0e26abfd7614ac", "shasum": "" }, "require": { "composer/semver": "^3.0", - "php": "^8.1" + "php": "^8.2" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.14", @@ -1242,16 +1242,16 @@ "type": "library", "extra": { "merge-plugin": { - "ignore-duplicates": false, "include": [ "composer.local.json" ], + "recurse": true, + "replace": true, "merge-dev": true, "merge-extra": false, - "merge-extra-deep": false, "merge-scripts": false, - "recurse": true, - "replace": true + "merge-extra-deep": false, + "ignore-duplicates": false } }, "autoload": { @@ -1284,26 +1284,26 @@ ], "support": { "issues": "https://github.com/psr-discovery/discovery/issues", - "source": "https://github.com/psr-discovery/discovery/tree/1.1.2" + "source": "https://github.com/psr-discovery/discovery/tree/1.2.0" }, - "time": "2024-08-09T07:04:30+00:00" + "time": "2024-12-05T16:59:22+00:00" }, { "name": "psr-discovery/event-dispatcher-implementations", - "version": "1.1.1", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/psr-discovery/event-dispatcher-implementations.git", - "reference": "9033bb984613703e4c4f795ef0657184dc1c70eb" + "reference": "8ccb36eca9c7a685d91316d1f64cff6253e38825" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/psr-discovery/event-dispatcher-implementations/zipball/9033bb984613703e4c4f795ef0657184dc1c70eb", - "reference": "9033bb984613703e4c4f795ef0657184dc1c70eb", + "url": "https://api.github.com/repos/psr-discovery/event-dispatcher-implementations/zipball/8ccb36eca9c7a685d91316d1f64cff6253e38825", + "reference": "8ccb36eca9c7a685d91316d1f64cff6253e38825", "shasum": "" }, "require": { - "php": "^8.1", + "php": "^8.2", "psr-discovery/discovery": "^1.0", "psr/event-dispatcher": "^1.0" }, @@ -1320,16 +1320,16 @@ "type": "library", "extra": { "merge-plugin": { - "ignore-duplicates": false, "include": [ "composer.local.json" ], + "recurse": true, + "replace": true, "merge-dev": true, "merge-extra": false, - "merge-extra-deep": false, "merge-scripts": false, - "recurse": true, - "replace": true + "merge-extra-deep": false, + "ignore-duplicates": false } }, "autoload": { @@ -1357,26 +1357,26 @@ ], "support": { "issues": "https://github.com/psr-discovery/event-dispatcher-implementations/issues", - "source": "https://github.com/psr-discovery/event-dispatcher-implementations/tree/1.1.1" + "source": "https://github.com/psr-discovery/event-dispatcher-implementations/tree/1.2.0" }, - "time": "2024-03-04T21:27:10+00:00" + "time": "2024-12-05T17:29:26+00:00" }, { "name": "psr-discovery/http-client-implementations", - "version": "1.2.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/psr-discovery/http-client-implementations.git", - "reference": "a05c54087d13504d8e48c27395fbab638fb0a114" + "reference": "3999d98e4fcbf099efeda07df82c59134f932ad8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/psr-discovery/http-client-implementations/zipball/a05c54087d13504d8e48c27395fbab638fb0a114", - "reference": "a05c54087d13504d8e48c27395fbab638fb0a114", + "url": "https://api.github.com/repos/psr-discovery/http-client-implementations/zipball/3999d98e4fcbf099efeda07df82c59134f932ad8", + "reference": "3999d98e4fcbf099efeda07df82c59134f932ad8", "shasum": "" }, "require": { - "php": "^8.1", + "php": "^8.2", "psr-discovery/discovery": "^1.0", "psr/http-client": "^1.0" }, @@ -1393,16 +1393,16 @@ "type": "library", "extra": { "merge-plugin": { - "ignore-duplicates": false, "include": [ "composer.local.json" ], + "recurse": true, + "replace": true, "merge-dev": true, "merge-extra": false, - "merge-extra-deep": false, "merge-scripts": false, - "recurse": true, - "replace": true + "merge-extra-deep": false, + "ignore-duplicates": false } }, "autoload": { @@ -1430,26 +1430,26 @@ ], "support": { "issues": "https://github.com/psr-discovery/http-client-implementations/issues", - "source": "https://github.com/psr-discovery/http-client-implementations/tree/1.2.0" + "source": "https://github.com/psr-discovery/http-client-implementations/tree/1.4.0" }, - "time": "2024-03-16T05:29:47+00:00" + "time": "2024-12-05T18:08:01+00:00" }, { "name": "psr-discovery/http-factory-implementations", - "version": "1.1.1", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/psr-discovery/http-factory-implementations.git", - "reference": "4ee07ae795b794e61578db32b5422a780b01b833" + "reference": "3979e3d9a95bedd91c13e1de12c6e99a74c449d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/psr-discovery/http-factory-implementations/zipball/4ee07ae795b794e61578db32b5422a780b01b833", - "reference": "4ee07ae795b794e61578db32b5422a780b01b833", + "url": "https://api.github.com/repos/psr-discovery/http-factory-implementations/zipball/3979e3d9a95bedd91c13e1de12c6e99a74c449d3", + "reference": "3979e3d9a95bedd91c13e1de12c6e99a74c449d3", "shasum": "" }, "require": { - "php": "^8.1", + "php": "^8.2", "psr-discovery/discovery": "^1.1", "psr/http-factory": "^1.0" }, @@ -1466,16 +1466,16 @@ "type": "library", "extra": { "merge-plugin": { - "ignore-duplicates": false, "include": [ "composer.local.json" ], + "recurse": true, + "replace": true, "merge-dev": true, "merge-extra": false, - "merge-extra-deep": false, "merge-scripts": false, - "recurse": true, - "replace": true + "merge-extra-deep": false, + "ignore-duplicates": false } }, "autoload": { @@ -1503,26 +1503,26 @@ ], "support": { "issues": "https://github.com/psr-discovery/http-factory-implementations/issues", - "source": "https://github.com/psr-discovery/http-factory-implementations/tree/1.1.1" + "source": "https://github.com/psr-discovery/http-factory-implementations/tree/1.2.0" }, - "time": "2024-03-04T21:31:16+00:00" + "time": "2024-12-05T17:18:21+00:00" }, { "name": "psr-discovery/log-implementations", - "version": "1.0.1", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/psr-discovery/log-implementations.git", - "reference": "384894384663fa5e1b2186112fb8ffe3f81a0b22" + "reference": "8cc55fcaa6b7481244e491a005263459fdfc6cba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/psr-discovery/log-implementations/zipball/384894384663fa5e1b2186112fb8ffe3f81a0b22", - "reference": "384894384663fa5e1b2186112fb8ffe3f81a0b22", + "url": "https://api.github.com/repos/psr-discovery/log-implementations/zipball/8cc55fcaa6b7481244e491a005263459fdfc6cba", + "reference": "8cc55fcaa6b7481244e491a005263459fdfc6cba", "shasum": "" }, "require": { - "php": "^8.1", + "php": "^8.2", "psr-discovery/discovery": "^1.0", "psr/log": "^1.0 | ^2.0 | ^3.0" }, @@ -1539,16 +1539,16 @@ "type": "library", "extra": { "merge-plugin": { - "ignore-duplicates": false, "include": [ "composer.local.json" ], + "recurse": true, + "replace": true, "merge-dev": true, "merge-extra": false, - "merge-extra-deep": false, "merge-scripts": false, - "recurse": true, - "replace": true + "merge-extra-deep": false, + "ignore-duplicates": false } }, "autoload": { @@ -1578,9 +1578,9 @@ ], "support": { "issues": "https://github.com/psr-discovery/log-implementations/issues", - "source": "https://github.com/psr-discovery/log-implementations/tree/1.0.1" + "source": "https://github.com/psr-discovery/log-implementations/tree/1.1.0" }, - "time": "2024-03-04T21:32:27+00:00" + "time": "2024-12-05T17:06:04+00:00" }, { "name": "psr/cache", @@ -1988,6 +1988,54 @@ }, "time": "2019-03-08T08:55:37+00:00" }, + { + "name": "square/pjson", + "version": "v0.5.0", + "source": { + "type": "git", + "url": "https://github.com/square/pjson.git", + "reference": "cf9f9a7810ad7287b30658f60c0bbbba80217319" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/square/pjson/zipball/cf9f9a7810ad7287b30658f60c0bbbba80217319", + "reference": "cf9f9a7810ad7287b30658f60c0bbbba80217319", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "orchestra/testbench": "^7.11", + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.7", + "symfony/var-dumper": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Square\\Pjson\\": "src/", + "Square\\Pjson\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Seb", + "email": "sebastien@squareup.com" + } + ], + "description": "Library for JSON <=> PHP model serialization / deserialization. Deserialize JSON directly into your object model PHP classes.", + "support": { + "issues": "https://github.com/square/pjson/issues", + "source": "https://github.com/square/pjson/tree/v0.5.0" + }, + "time": "2024-03-15T18:19:22+00:00" + }, { "name": "symfony/deprecation-contracts", "version": "v3.5.1", diff --git a/src/lib/Domain/History.php b/src/lib/Domain/History.php index 27eea02..5a5f0ef 100644 --- a/src/lib/Domain/History.php +++ b/src/lib/Domain/History.php @@ -2,24 +2,46 @@ namespace MyPrayerJournal\Domain; -use JsonSerializable; +use BitBadger\InspiredByFSharp\Option; +use Square\Pjson\{Json, JsonSerialize}; /** * A record of an action taken on a request */ -class History implements JsonSerializable +class History { + use JsonSerialize; + + /** @var string The date/time this entry was made */ + #[Json] + public string $asOf = ''; + + /** @var RequestAction The action taken for this history entry */ + #[Json] + public RequestAction $action; + + /** @var string|null The text for this history entry (optional) */ + #[Json('text', omit_empty: true)] + private ?string $dbText { + get => $this->text->unwrap(); + set { $this->text = Option::of($value); } + } + + /** @var Option The text for this history entry */ + public Option $text { + get => $this->text ?? Option::None(); + set { $this->text = $value; } + } + /** * @param string $asOf The date/time this entry was made * @param RequestAction $action The action taken for this history entry * @param string|null $text The text for this history entry (optional) */ - public function __construct(public string $asOf, public RequestAction $action, public ?string $text = null) { } - - public function jsonSerialize(): mixed + public function __construct(string $asOf, RequestAction $action, ?string $text = null) { - $values = ['asOf' => $this->asOf, 'action' => $this->action->value]; - if (isset($this->text)) $values['text'] = $this->text; - return $values; + $this->asOf = $asOf; + $this->action = $action; + $this->dbText = $text; } } diff --git a/src/lib/Domain/Note.php b/src/lib/Domain/Note.php index 4100cec..c749e17 100644 --- a/src/lib/Domain/Note.php +++ b/src/lib/Domain/Note.php @@ -4,17 +4,20 @@ namespace MyPrayerJournal\Domain; use BitBadger\InspiredByFSharp\Option; use BitBadger\PDODocument\DocumentException; +use Square\Pjson\{Json, JsonSerialize}; /** * A note entered on a prayer request */ class Note { + use JsonSerialize; + /** * @param string $asOf The date/time this note was recorded * @param string $text The text of the note */ - public function __construct(public string $asOf, public string $text) { } + public function __construct(#[Json] public string $asOf, #[Json] public string $text) { } /** * Retrieve notes for a given request diff --git a/src/lib/Domain/Recurrence.php b/src/lib/Domain/Recurrence.php index 364efee..cebce82 100644 --- a/src/lib/Domain/Recurrence.php +++ b/src/lib/Domain/Recurrence.php @@ -2,19 +2,46 @@ namespace MyPrayerJournal\Domain; +use BitBadger\InspiredByFSharp\Option; use DateInterval; -use JsonSerializable; +use Square\Pjson\{Json, JsonSerialize}; /** * The recurrence for a prayer request */ -class Recurrence implements JsonSerializable +class Recurrence { + use JsonSerialize; + + /** @var RecurrencePeriod The recurrence period */ + #[Json] + public RecurrencePeriod $period { + get => $this->period ?? RecurrencePeriod::Immediate; + set { $this->period = $value; } + } + + /** @var int|null How many of the periods will pass before the request is visible again */ + #[Json('interval', omit_empty: true)] + private ?int $dbInterval { + get => $this->interval->unwrap(); + set { $this->interval = Option::of($value); } + } + + /** @var Option How many of the periods will pass before the request is visible again */ + public Option $interval { + get => $this->interval ?? Option::None(); + set { $this->interval = $value; } + } + /** * @param RecurrencePeriod $period The recurrence period * @param int|null $interval How many of the periods will pass before the request is visible again */ - public function __construct(public RecurrencePeriod $period, public ?int $interval = null) { } + public function __construct(RecurrencePeriod $period, ?int $interval = null) + { + $this->period = $period; + $this->dbInterval = $interval; + } /** * Get the date/time interval for this recurrence @@ -25,17 +52,10 @@ class Recurrence implements JsonSerializable { $period = match ($this->period) { RecurrencePeriod::Immediate => 'T0S', - RecurrencePeriod::Hours => "T{$this->interval}H", - RecurrencePeriod::Days => "{$this->interval}D", - RecurrencePeriod::Weeks => ($this->interval * 7) . 'D' + RecurrencePeriod::Hours => "T{$this->interval->value}H", + RecurrencePeriod::Days => "{$this->interval->value}D", + RecurrencePeriod::Weeks => ($this->interval->value * 7) . 'D' }; return new DateInterval("P$period"); } - - public function jsonSerialize(): mixed - { - $values = ['period' => $this->period->value]; - if (isset($this->interval)) $values['interval'] = $this->interval; - return $values; - } } diff --git a/src/lib/Domain/Request.php b/src/lib/Domain/Request.php index 599bcba..bf900a8 100644 --- a/src/lib/Domain/Request.php +++ b/src/lib/Domain/Request.php @@ -7,71 +7,82 @@ use BitBadger\PDODocument\{Custom, DocumentException, DocumentList, Find}; use BitBadger\PDODocument\Mapper\DocumentMapper; use DateTimeImmutable; use Exception; -use JsonSerializable; use MyPrayerJournal\Table; +use Square\Pjson\{Json, JsonSerialize}; /** * A prayer request */ -class Request implements JsonSerializable +class Request { - /** - * @param string $id The ID for the request - * @param string $enteredOn The date/time this request was originally entered - * @param string $userId The ID of the user to whom this request belongs - * @param string|null $snoozedUntil The date/time the snooze expires for this request (null = not snoozed) - * @param string|null $showAfter The date/time the current recurrence period is over (null = immediate) - * @param Recurrence $recurrence The recurrence for this request - * @param History[] $history The history of this request - * @param Note[] $notes Notes regarding this request - * @throws Exception If the ID generation fails - */ - public function __construct(public string $id = '', public string $enteredOn = '', public string $userId = '', - public ?string $snoozedUntil = null, public ?string $showAfter = null, - public Recurrence $recurrence = new Recurrence(RecurrencePeriod::Immediate), - public array $history = [], public array $notes = []) { } + use JsonSerialize; - /** - * Get the current text for this request - * - * @return string The most recent text for the request - */ - public function currentText(): string - { - foreach ($this->history as $hist) if (isset($hist->text)) return $hist->text; - return ''; + /** @var string The ID for the request */ + #[Json] + public string $id = ''; + + /** @var string The date/time this request was originally entered */ + #[Json] + public string $enteredOn = ''; + + /** @var string The ID of the user to whom this request belongs */ + #[Json] + public string $userId = ''; + + /** @var string|null The date/time the snooze expires for this request (null = not snoozed) */ + #[Json('snoozedUntil', omit_empty: true)] + private ?string $dbSnoozedUntil { + get => $this->snoozedUntil->unwrap(); + set { $this->snoozedUntil = Option::of($value); } } - /** - * Get the date/time this request was last marked as prayed - * - * @return string|null The date/time this request was last marked as prayed - */ - public function lastPrayed(): ?string - { - foreach ($this->history as $hist) if ($hist->action == RequestAction::Prayed) return $hist->asOf; - return null; + /** @var Option The date/time the snooze expires for this request (None = not snoozed) */ + public Option $snoozedUntil { + get => $this->snoozedUntil ?? Option::None(); + set { $this->snoozedUntil = $value; } } - /** - * Has this request been answered? - * - * @return bool True if the request is answered, false if not - */ - public function isAnswered(): bool - { - return $this->history[0]->action === RequestAction::Answered; + /** @var string|null The date/time the current recurrence period is over (null = immediate) */ + #[Json(omit_empty: true)] + public ?string $showAfter = null; + + /** @var Recurrence The recurrence for this request */ + #[Json] + public Recurrence $recurrence { + get => $this->recurrence ?? new Recurrence(RecurrencePeriod::Immediate); + set => $this->recurrence = $value; } - /** - * Is this request currently snoozed? - * - * @return bool True if the request is snoozed, false if not - * @throws Exception If the snoozed until date/time is not valid - */ - public function isSnoozed(): bool - { - return isset($this->snoozedUntil) && new DateTimeImmutable($this->snoozedUntil) > new DateTimeImmutable('now'); + /** @var History[] The history of this request */ + #[Json(type: History::class)] + public array $history = []; + + /** @param Note[] $notes Notes regarding this request */ + #[Json(type: Note::class)] + public array $notes = []; + + /** The current text for this request */ + public string $currentText { + get => Option::of(array_find($this->history, fn(History $it) => $it->text->isSome)) + ->map(fn(History $it) => $it->text->value) + ->getOrDefault(''); + } + + /** @var Option The date/time this request was last marked as prayed */ + public Option $lastPrayed { + get => Option::of(array_find($this->history, fn(History $it) => $it->action === RequestAction::Prayed)) + ->map(fn(History $it) => $it->asOf); + } + + /** Has this request been answered? */ + public bool $isAnswered { + get => $this->history[0]->action === RequestAction::Answered; + } + + /** Is this request currently snoozed? */ + public bool $isSnoozed { + get => $this->snoozedUntil->isSome + && new DateTimeImmutable($this->snoozedUntil->value) > new DateTimeImmutable('now'); } /** @@ -80,28 +91,12 @@ class Request implements JsonSerializable * @return bool True if the request is pending, false if not * @throws Exception If the snoozed or show-after date/times are not valid */ - public function isPending(): bool - { - return !$this->isSnoozed() + public bool $isPending { + get => !$this->isSnoozed && isset($this->showAfter) && new DateTimeImmutable($this->showAfter) > new DateTimeImmutable('now'); } - public function jsonSerialize(): mixed - { - $values = [ - 'id' => $this->id, - 'enteredOn' => $this->enteredOn, - 'userId' => $this->userId, - 'recurrence' => $this->recurrence, - 'history' => $this->history, - 'notes' => $this->notes - ]; - if (isset($this->snoozedUntil)) $values['snoozedUntil'] = $this->snoozedUntil; - if (isset($this->showAfter)) $values['showAfter'] = $this->showAfter; - return $values; - } - /** * Find a request by its ID * @@ -112,7 +107,7 @@ class Request implements JsonSerializable public static function byId(string $id): Option { return Find::byId(Table::REQUEST, $id, self::class) - ->map(fn(Request $it) => $it->userId === $_SESSION['user_id'] ? Option::Some($it) : Option::None()); + ->map(fn(Request $it) => $it->userId === $_SESSION['user_id'] ? $it : null); } /** diff --git a/src/lib/UI/Component.php b/src/lib/UI/Component.php index 831d3fa..d5ccde8 100644 --- a/src/lib/UI/Component.php +++ b/src/lib/UI/Component.php @@ -55,13 +55,13 @@ class Component title="Mark as Prayed">
-

currentText());?> +

currentText);?>

@@ -135,26 +135,24 @@ class Component
id?>>id", self::icon('description'), ['class' => $btnClass, 'title' => 'View Full Request']); - if (!$req->isAnswered()) { + if (!$req->isAnswered) { echo self::pageLink("/request/edit?id=$req->id", self::icon('edit'), ['class' => $btnClass, 'title' => 'Edit Request']); } - if ($req->isSnoozed()) { + if ($req->isSnoozed) { echo $restoreBtn($req->id, 'cancel-snooze', 'Cancel Snooze'); - } elseif ($req->isPending()) { + } elseif ($req->isPending) { echo $restoreBtn($req->id, 'show', 'Show Now'); } - echo '

' . $req->currentText(); - if ($req->isSnoozed() || $req->isPending() || $req->isAnswered()) { ?> + echo '

' . htmlentities($req->currentText); + if ($req->isSnoozed || $req->isPending || $req->isAnswered) { ?>
isSnoozed()) { - echo 'Snooze expires ' . self::relativeDate($req->snoozedUntil); - } elseif ($req->isPending()) { - echo 'Request appears next ' . self::relativeDate($req->showAfter); - } else { - echo 'Answered ' . self::relativeDate($req->history[0]->asOf); - } ?> + echo match (true) { + $req->isSnoozed => 'Snooze expires ' . self::relativeDate($req->snoozedUntil->value), + $req->isPending => 'Request appears next ' . self::relativeDate($req->showAfter), + default => 'Answered ' . self::relativeDate($req->history[0]->asOf) + };?>

id, ['snoozedUntil']); -$req->snoozedUntil = null; +$req->snoozedUntil = Option::None(); // TODO: message Layout::bareHead(); diff --git a/src/public/request/edit.php b/src/public/request/edit.php index 8b32468..69f8aaf 100644 --- a/src/public/request/edit.php +++ b/src/public/request/edit.php @@ -31,7 +31,7 @@ Layout::pageHead("$action Prayer Request");?> >
+ placeholder="Enter the text of the request" autofocus required>currentText?>

@@ -72,23 +72,23 @@ Layout::pageHead("$action Prayer Request");?>
recurrence->interval ?? 0?> style="width:6rem;"recurrence->period == RecurrencePeriod::Immediate) echo ' disabled'; ?>> + value=recurrence->interval->getOrDefault(0)?> style="width:6rem;"recurrence->period === RecurrencePeriod::Immediate) echo ' disabled'; ?>>
diff --git a/src/public/request/full.php b/src/public/request/full.php index 12a1935..d4a6979 100644 --- a/src/public/request/full.php +++ b/src/public/request/full.php @@ -7,7 +7,7 @@ require '../../start.php'; $req = validate_request($_GET['id'], ['GET']); -$answered = $req->isAnswered() ? new DateTimeImmutable($req->history[0]->asOf) : null; +$answered = $req->isAnswered ? new DateTimeImmutable($req->history[0]->asOf) : null; $prayed = sizeof(array_filter($req->history, fn(History $hist) => $hist->action == RequestAction::Prayed)); $daysOpen = (($answered ?? new DateTimeImmutable('now'))->getTimestamp() @@ -15,10 +15,10 @@ $daysOpen = $logs = array_merge( array_map(fn(Note $note) => [new DateTimeImmutable($note->asOf), 'Notes', $note->text], $req->notes), - array_map(fn(History $hist) => [new DateTimeImmutable($hist->asOf), $hist->action->value, $hist->text ?? ''], + array_map(fn(History $hist) => [new DateTimeImmutable($hist->asOf), $hist->action->value, $hist->text->getOrDefault('')], $req->history)); usort($logs, fn($a, $b) => $a[0] > $b[0] ? -1 : 1); -if ($req->isAnswered()) array_shift($logs); +if ($req->isAnswered) array_shift($logs); Layout::pageHead('Full Request');?>
@@ -32,7 +32,7 @@ Layout::pageHead('Full Request');?> } ?> Prayed times • Open days -

currentText())?> +

currentText)?>

    diff --git a/src/public/request/save.php b/src/public/request/save.php index f085b94..6c25727 100644 --- a/src/public/request/save.php +++ b/src/public/request/save.php @@ -1,5 +1,6 @@ period <> RecurrencePeriod::Immediate) { - $recurrence->interval = (int)($_POST['recurCount'] ?? $_PATCH['recurCount']); + $recurrence->interval = Option::of((int)($_POST['recurCount'] ?? $_PATCH['recurCount'])); } switch ($_SERVER['REQUEST_METHOD']) { case 'POST': - Document::insert(Table::REQUEST, new Request( - enteredOn: $now->format('c'), - userId: $_SESSION['user_id'], - recurrence: $recurrence, - history: [new History($now->format('c'), RequestAction::Created, $_POST['requestText'])])); + $req = new Request(); + $req->enteredOn = $now->format('c'); + $req->userId = $_SESSION['user_id']; + $req->recurrence = $recurrence; + $req->history = [new History($now->format('c'), RequestAction::Created, $_POST['requestText'])]; + Document::insert(Table::REQUEST, $req); //Messages.pushSuccess ctx "Added prayer request" "/journal" see_other('/journal'); @@ -37,7 +39,7 @@ switch ($_SERVER['REQUEST_METHOD']) { } // append history $upd8Text = trim($_PATCH['requestText']); - $text = empty($upd8Text) || $upd8Text === $req->currentText() ? null : $upd8Text; + $text = empty($upd8Text) || $upd8Text === $req->currentText ? null : $upd8Text; array_unshift($req->history, new History($now->format('c'), RequestAction::from($_PATCH['status']), $text)); $patch['history'] = $req->history; Patch::byId(Table::REQUEST, $req->id, $patch); diff --git a/src/public/user/log-on.php b/src/public/user/log-on/index.php similarity index 81% rename from src/public/user/log-on.php rename to src/public/user/log-on/index.php index 34baaa1..31edc05 100644 --- a/src/public/user/log-on.php +++ b/src/public/user/log-on/index.php @@ -2,7 +2,7 @@ use MyPrayerJournal\Auth; -require '../../start.php'; +require '../../../start.php'; if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found(); Auth::logOn(); diff --git a/src/start.php b/src/start.php index b85ffee..c37de5c 100644 --- a/src/start.php +++ b/src/start.php @@ -9,7 +9,7 @@ use MyPrayerJournal\Domain\Request; require __DIR__ . '/vendor/autoload.php'; /** The version of this application */ -const MPJ_VERSION = '4.0.0-beta1'; +const MPJ_VERSION = '4.0.0-beta2'; (Dotenv::createImmutable(__DIR__))->load();