Auth works; empty journal works
This commit is contained in:
parent
7421f9c788
commit
907d759a23
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -257,3 +257,4 @@ paket-files/
|
||||||
|
|
||||||
# in-progress: PHP version
|
# in-progress: PHP version
|
||||||
src/app/vendor
|
src/app/vendor
|
||||||
|
**/.env
|
||||||
|
|
98
src/app/AppUser.php
Normal file
98
src/app/AppUser.php
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MyPrayerJournal;
|
||||||
|
|
||||||
|
use Auth0\SDK\Auth0;
|
||||||
|
|
||||||
|
class AppUser
|
||||||
|
{
|
||||||
|
/** The Auth0 client instance to use for authentication */
|
||||||
|
private static ?Auth0 $auth0 = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Auth0 instance
|
||||||
|
*
|
||||||
|
* @return Auth0 The Auth0 instance, lazily initialized
|
||||||
|
*/
|
||||||
|
private static function auth0Instance(): Auth0
|
||||||
|
{
|
||||||
|
if (is_null(self::$auth0)) {
|
||||||
|
self::$auth0 = new \Auth0\SDK\Auth0([
|
||||||
|
'domain' => $_ENV['AUTH0_DOMAIN'],
|
||||||
|
'clientId' => $_ENV['AUTH0_CLIENT_ID'],
|
||||||
|
'clientSecret' => $_ENV['AUTH0_CLIENT_SECRET'],
|
||||||
|
'cookieSecret' => $_ENV['AUTH0_COOKIE_SECRET']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return self::$auth0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the host to use for return URLs
|
||||||
|
*
|
||||||
|
* @return string The host for return URLs
|
||||||
|
*/
|
||||||
|
private static function host()
|
||||||
|
{
|
||||||
|
return 'http' . ($_SERVER['SERVER_PORT'] == 443 ? 's' : '' ) . "://{$_SERVER['HTTP_HOST']}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the log on callback URL
|
||||||
|
*
|
||||||
|
* @return string The log on callback URL
|
||||||
|
*/
|
||||||
|
private static function logOnCallback()
|
||||||
|
{
|
||||||
|
return self::host() . '/user/log-on/success';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate a redirect to the Auth0 log on page
|
||||||
|
*
|
||||||
|
* @param string $nextUrl The URL (other than /journal) to which the user should be redirected
|
||||||
|
* @return never This function exits the currently running script
|
||||||
|
*/
|
||||||
|
public static function logOn(?string $nextUrl = null): never
|
||||||
|
{
|
||||||
|
// TODO: pass the next URL in the Auth0 callback
|
||||||
|
self::auth0Instance()->clear();
|
||||||
|
header('Location: ' . self::auth0Instance()->login(self::logOnCallback()));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the log on response from Auth0
|
||||||
|
*
|
||||||
|
* @return never This function exits the currently running script
|
||||||
|
*/
|
||||||
|
public static function processLogOn(): never
|
||||||
|
{
|
||||||
|
self::auth0Instance()->exchange(self::logOnCallback());
|
||||||
|
// TODO: check for next URL and redirect if present
|
||||||
|
header('Location: /journal');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log off the current user
|
||||||
|
*
|
||||||
|
* @return never This function exits the currently running script
|
||||||
|
*/
|
||||||
|
public static function logOff(): never
|
||||||
|
{
|
||||||
|
header('Location: ' . self::auth0Instance()->logout(self::host() . '/'));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current user
|
||||||
|
*
|
||||||
|
* @return ?object The current user, or null if one is not signed in
|
||||||
|
*/
|
||||||
|
public static function current(): ?object
|
||||||
|
{
|
||||||
|
return self::auth0Instance()->getCredentials();
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,6 @@ class Data
|
||||||
*/
|
*/
|
||||||
public static function configure()
|
public static function configure()
|
||||||
{
|
{
|
||||||
Configuration::$connectionString = 'pgsql:host=localhost;port=5432;dbname=leafjson;user=leaf;password=leaf';
|
|
||||||
Configuration::$startUp = '\MyPrayerJournal\Data::startUp';
|
Configuration::$startUp = '\MyPrayerJournal\Data::startUp';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
63
src/app/Handlers.php
Normal file
63
src/app/Handlers.php
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MyPrayerJournal;
|
||||||
|
|
||||||
|
class Handlers
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Render a BareUI template
|
||||||
|
*
|
||||||
|
* @param string $template The template name to render
|
||||||
|
* @param string $pageTitle The title for the page
|
||||||
|
* @param ?array $params Parameters to use to render the page (optional)
|
||||||
|
*/
|
||||||
|
public static function render(string $template, string $pageTitle, ?array $params = null)
|
||||||
|
{
|
||||||
|
$params = array_merge($params ?? [], [
|
||||||
|
'pageTitle' => $pageTitle,
|
||||||
|
'isHtmx' =>
|
||||||
|
array_key_exists('HTTP_HX_REQUEST', $_SERVER)
|
||||||
|
&& (!array_key_exists('HTTP_HX_HISTORY_RESTORE_REQUEST', $_SERVER)),
|
||||||
|
'user' => AppUser::current(),
|
||||||
|
'hasSnoozed' => false,
|
||||||
|
]);
|
||||||
|
$params['pageContent'] = app()->template->render($template, $params);
|
||||||
|
// TODO: make the htmx distinction here
|
||||||
|
response()->markup(app()->template->render('layout/full', $params));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a BareUI component template
|
||||||
|
*
|
||||||
|
* @param string $template The template name to render
|
||||||
|
* @param ?array $params Parameter to use to render the component (optional)
|
||||||
|
*/
|
||||||
|
private static function renderComponent(string $template, ?array $params = null)
|
||||||
|
{
|
||||||
|
$params = $params ?? [];
|
||||||
|
$params['pageContent'] = app()->template->render($template, $params);
|
||||||
|
header('Cache-Control: no-cache, max-age=-1');
|
||||||
|
response()->markup(app()->template->render('layout/component', $params));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** GET: /journal */
|
||||||
|
public static function journal()
|
||||||
|
{
|
||||||
|
if (!AppUser::current()) AppUser::logOn();
|
||||||
|
|
||||||
|
$user = AppUser::current()->user;
|
||||||
|
$firstName = (array_key_exists('given_name', $user) ? $user['given_name'] : null) ?? 'Your';
|
||||||
|
self::render('journal', $firstName . ($firstName == 'Your' ? '' : '’s') . ' Prayer Journal');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** GET: /components/journal-items */
|
||||||
|
public static function journalItems()
|
||||||
|
{
|
||||||
|
if (!AppUser::current()) AppUser::logOn();
|
||||||
|
|
||||||
|
self::renderComponent('components/journal_items', [
|
||||||
|
'requests' => Data::getJournal(AppUser::current()->user['sub'])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,12 @@
|
||||||
"leafs/bareui": "^1.1",
|
"leafs/bareui": "^1.1",
|
||||||
"leafs/db": "^2.1",
|
"leafs/db": "^2.1",
|
||||||
"netresearch/jsonmapper": "^4.2",
|
"netresearch/jsonmapper": "^4.2",
|
||||||
"visus/cuid2": "^3.0"
|
"visus/cuid2": "^3.0",
|
||||||
|
"guzzlehttp/guzzle": "^7.8",
|
||||||
|
"guzzlehttp/psr7": "^2.6",
|
||||||
|
"http-interop/http-factory-guzzle": "^1.2",
|
||||||
|
"auth0/auth0-php": "^8.7",
|
||||||
|
"vlucas/phpdotenv": "^5.5"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
@ -12,5 +17,10 @@
|
||||||
"MyPrayerJournal\\": [ "." ],
|
"MyPrayerJournal\\": [ "." ],
|
||||||
"MyPrayerJournal\\Domain\\": [ "./domain" ]
|
"MyPrayerJournal\\Domain\\": [ "./domain" ]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"allow-plugins": {
|
||||||
|
"php-http/discovery": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2186
src/app/composer.lock
generated
2186
src/app/composer.lock
generated
File diff suppressed because it is too large
Load Diff
|
@ -28,7 +28,7 @@ class Configuration
|
||||||
$db = $_ENV['PGDOC_DB'] ?? 'postgres';
|
$db = $_ENV['PGDOC_DB'] ?? 'postgres';
|
||||||
$user = $_ENV['PGDOC_USER'] ?? 'postgres';
|
$user = $_ENV['PGDOC_USER'] ?? 'postgres';
|
||||||
$pass = $_ENV['PGDOC_PASS'] ?? 'postgres';
|
$pass = $_ENV['PGDOC_PASS'] ?? 'postgres';
|
||||||
self::$connectionString = "pgsql:host=$host;port=$port;dbname=$db;user=$user;pass=$pass";
|
self::$connectionString = "pgsql:host=$host;port=$port;dbname=$db;user=$user;password=$pass";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -405,7 +405,7 @@ class Document
|
||||||
*/
|
*/
|
||||||
private static function createCustomQuery(string $sql, array $params): PDOStatement
|
private static function createCustomQuery(string $sql, array $params): PDOStatement
|
||||||
{
|
{
|
||||||
$query = pdo()->prepare($sql, self::NO_PREPARE);
|
$query = pdo()->prepare($sql, [ \PDO::ATTR_EMULATE_PREPARES => false ]);
|
||||||
array_walk($params, fn ($value, $name) => $query->bindParam($name, $value));
|
array_walk($params, fn ($value, $name) => $query->bindParam($name, $value));
|
||||||
$query->execute();
|
$query->execute();
|
||||||
return $query;
|
return $query;
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
require __DIR__ . '/vendor/autoload.php';
|
require __DIR__ . '/vendor/autoload.php';
|
||||||
|
(Dotenv\Dotenv::createImmutable(__DIR__))->load();
|
||||||
|
|
||||||
use MyPrayerJournal\Data;
|
use MyPrayerJournal\{ AppUser, Data, Handlers };
|
||||||
|
|
||||||
Data::configure();
|
Data::configure();
|
||||||
|
|
||||||
|
@ -18,31 +19,26 @@ app()->template->config('params', [
|
||||||
'version' => 'v4',
|
'version' => 'v4',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function renderPage(string $template, array $params, string $pageTitle)
|
|
||||||
|
app()->get('/', fn () => Handlers::render('home', 'Welcome'));
|
||||||
|
|
||||||
|
app()->get('/components/journal-items', Handlers::journalItems(...));
|
||||||
|
|
||||||
|
app()->get('/journal', Handlers::journal(...));
|
||||||
|
|
||||||
|
app()->get('/legal/privacy-policy', fn () => Handlers::render('legal/privacy-policy', 'Privacy Policy'));
|
||||||
|
app()->get('/legal/terms-of-service', fn () => Handlers::render('legal/terms-of-service', 'Terms of Service'));
|
||||||
|
|
||||||
|
app()->get('/user/log-on', AppUser::logOn(...));
|
||||||
|
app()->get('/user/log-on/success', AppUser::processLogOn(...));
|
||||||
|
app()->get('/user/log-off', AppUser::logOff(...));
|
||||||
|
|
||||||
|
// TODO: remove before go-live
|
||||||
|
$stdOut = fopen('php://stdout', 'w');
|
||||||
|
function stdout(string $msg)
|
||||||
{
|
{
|
||||||
if (is_null($params)) {
|
global $stdOut;
|
||||||
$params = [];
|
fwrite($stdOut, $msg . "\n");
|
||||||
}
|
}
|
||||||
$params['pageTitle'] = $pageTitle;
|
|
||||||
$params['isHtmx'] =
|
|
||||||
array_key_exists('HTTP_HX_REQUEST', $_SERVER)
|
|
||||||
&& (!array_key_exists('HTTP_HX_HISTORY_RESTORE_REQUEST', $_SERVER));
|
|
||||||
$params['userId'] = false;
|
|
||||||
$params['pageContent'] = app()->template->render($template, $params);
|
|
||||||
// TODO: make the htmx distinction here
|
|
||||||
response()->markup(app()->template->render('layout/full', $params));
|
|
||||||
}
|
|
||||||
|
|
||||||
app()->get('/', function () {
|
|
||||||
renderPage('home', [], 'Welcome');
|
|
||||||
});
|
|
||||||
|
|
||||||
app()->get('/legal/privacy-policy', function () {
|
|
||||||
renderPage('legal/privacy-policy', [], 'Privacy Policy');
|
|
||||||
});
|
|
||||||
|
|
||||||
app()->get('/legal/terms-of-service', function () {
|
|
||||||
renderPage('legal/terms-of-service', [], 'Terms of Service');
|
|
||||||
});
|
|
||||||
|
|
||||||
app()->run();
|
app()->run();
|
||||||
|
|
16
src/app/pages/components/journal_items.view.php
Normal file
16
src/app/pages/components/journal_items.view.php
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
if (count($requests) == 0) {
|
||||||
|
echo app()->template->render('components/no_results', [
|
||||||
|
'heading' => 'No Active Requests',
|
||||||
|
'link' => '/request/new/edit',
|
||||||
|
'buttonText' => 'Add a Request',
|
||||||
|
'text' => 'You have no requests to be shown; see the “Active” link above for snoozed or '
|
||||||
|
. 'deferred requests, and the “Answered” link for answered requests'
|
||||||
|
]);
|
||||||
|
} else { ?>
|
||||||
|
<section id="journalItems" class="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 g-3" hx-target="this"
|
||||||
|
hx-swap="outerHTML" aria-label="Prayer Requests">
|
||||||
|
items
|
||||||
|
|> List.map (journalCard now tz)
|
||||||
|
</section><?php
|
||||||
|
}
|
7
src/app/pages/components/no_results.view.php
Normal file
7
src/app/pages/components/no_results.view.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<div class="card">
|
||||||
|
<h5 class="card-header"><?php echo $heading; ?></h5>
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<p class="card-text"><?php echo $text; ?></p>
|
||||||
|
<a <?php $page_link($link); ?> class="btn btn-primary"><?php echo $buttonText; ?></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
58
src/app/pages/journal.view.php
Normal file
58
src/app/pages/journal.view.php
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<article class="container-fluid mt-3">
|
||||||
|
<h2 class="pb-3"><?php echo $pageTitle; ?></h2>
|
||||||
|
<p class="pb-3 text-center">
|
||||||
|
<a <?php $page_link('/request/new/edit'); ?> class="btn btn-primary">
|
||||||
|
<span class="material-icons">add_box</span> Add a Prayer Request
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p hx-get="/components/journal-items" hx-swap="outerHTML" hx-trigger="load">
|
||||||
|
Loading your prayer journal…
|
||||||
|
</p>
|
||||||
|
<!--
|
||||||
|
div [ _id "notesModal"
|
||||||
|
_class "modal fade"
|
||||||
|
_tabindex "-1"
|
||||||
|
_ariaLabelledBy "nodesModalLabel"
|
||||||
|
_ariaHidden "true" ] [
|
||||||
|
div [ _class "modal-dialog modal-dialog-scrollable" ] [
|
||||||
|
div [ _class "modal-content" ] [
|
||||||
|
div [ _class "modal-header" ] [
|
||||||
|
h5 [ _class "modal-title"; _id "nodesModalLabel" ] [ str "Add Notes to Prayer Request" ]
|
||||||
|
button [ _type "button"; _class "btn-close"; _data "bs-dismiss" "modal"; _ariaLabel "Close" ] []
|
||||||
|
]
|
||||||
|
div [ _class "modal-body"; _id "notesBody" ] [ ]
|
||||||
|
div [ _class "modal-footer" ] [
|
||||||
|
button [ _type "button"
|
||||||
|
_id "notesDismiss"
|
||||||
|
_class "btn btn-secondary"
|
||||||
|
_data "bs-dismiss" "modal" ] [
|
||||||
|
str "Close"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
div [ _id "snoozeModal"
|
||||||
|
_class "modal fade"
|
||||||
|
_tabindex "-1"
|
||||||
|
_ariaLabelledBy "snoozeModalLabel"
|
||||||
|
_ariaHidden "true" ] [
|
||||||
|
div [ _class "modal-dialog modal-sm" ] [
|
||||||
|
div [ _class "modal-content" ] [
|
||||||
|
div [ _class "modal-header" ] [
|
||||||
|
h5 [ _class "modal-title"; _id "snoozeModalLabel" ] [ str "Snooze Prayer Request" ]
|
||||||
|
button [ _type "button"; _class "btn-close"; _data "bs-dismiss" "modal"; _ariaLabel "Close" ] []
|
||||||
|
]
|
||||||
|
div [ _class "modal-body"; _id "snoozeBody" ] [ ]
|
||||||
|
div [ _class "modal-footer" ] [
|
||||||
|
button [ _type "button"
|
||||||
|
_id "snoozeDismiss"
|
||||||
|
_class "btn btn-secondary"
|
||||||
|
_data "bs-dismiss" "modal" ] [
|
||||||
|
str "Close"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
] -->
|
||||||
|
</article>
|
|
@ -1,5 +1,5 @@
|
||||||
<head>
|
<head>
|
||||||
<title><?php echo htmlentities($pageTitle); ?> « myPrayerJournal</title><?php
|
<title><?php echo $pageTitle; ?> « myPrayerJournal</title><?php
|
||||||
if (!$isHtmx) { ?>
|
if (!$isHtmx) { ?>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="description" content="Online prayer journal - free w/Google or Microsoft account">
|
<meta name="description" content="Online prayer journal - free w/Google or Microsoft account">
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<span class="m">my</span><span class="p">Prayer</span><span class="j">Journal</span>
|
<span class="m">my</span><span class="p">Prayer</span><span class="j">Journal</span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="navbar-nav me-auto d-flex flex-row"><?php
|
<ul class="navbar-nav me-auto d-flex flex-row"><?php
|
||||||
if ($userId) { ?>
|
if ($user) { ?>
|
||||||
<li class="nav-item"><a <?php $page_link('/journal', true); ?>>Journal</a></li>
|
<li class="nav-item"><a <?php $page_link('/journal', true); ?>>Journal</a></li>
|
||||||
<li class="nav-item"><a <?php $page_link('/requests/active', true); ?>>Active</a></li><?php
|
<li class="nav-item"><a <?php $page_link('/requests/active', true); ?>>Active</a></li><?php
|
||||||
if ($hasSnoozed) { ?>
|
if ($hasSnoozed) { ?>
|
||||||
|
|
5
src/app/pages/layout/component.view.php
Normal file
5
src/app/pages/layout/component.view.php
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title></title></head>
|
||||||
|
<body><?php echo $pageContent; ?></body>
|
||||||
|
</html>
|
|
@ -3,7 +3,7 @@
|
||||||
<?php echo app()->template->render('layout/_head', [ 'pageTitle' => $pageTitle, 'isHtmx' => $isHtmx ]); ?>
|
<?php echo app()->template->render('layout/_head', [ 'pageTitle' => $pageTitle, 'isHtmx' => $isHtmx ]); ?>
|
||||||
<body>
|
<body>
|
||||||
<section id="top" aria-label="Top navigation">
|
<section id="top" aria-label="Top navigation">
|
||||||
<?php echo app()->template->render('layout/_nav', [ 'userId' => $userId ]); ?>
|
<?php echo app()->template->render('layout/_nav', [ 'user' => $user, 'hasSnoozed' => $hasSnoozed ]); ?>
|
||||||
<?php echo $pageContent; ?>
|
<?php echo $pageContent; ?>
|
||||||
</section>
|
</section>
|
||||||
<?php echo app()->template->render('layout/_foot', [ 'isHtmx' => $isHtmx ]); ?>
|
<?php echo app()->template->render('layout/_foot', [ 'isHtmx' => $isHtmx ]); ?>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user