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
|
||||
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,8 +16,7 @@ class Data
|
||||
*/
|
||||
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/db": "^2.1",
|
||||
"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": {
|
||||
"psr-4": {
|
||||
@ -12,5 +17,10 @@
|
||||
"MyPrayerJournal\\": [ "." ],
|
||||
"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';
|
||||
$user = $_ENV['PGDOC_USER'] ?? '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
|
||||
{
|
||||
$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));
|
||||
$query->execute();
|
||||
return $query;
|
||||
|
@ -1,14 +1,15 @@
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
(Dotenv\Dotenv::createImmutable(__DIR__))->load();
|
||||
|
||||
use MyPrayerJournal\Data;
|
||||
use MyPrayerJournal\{ AppUser, Data, Handlers };
|
||||
|
||||
Data::configure();
|
||||
|
||||
app()->template->config('path', './pages');
|
||||
app()->template->config('params', [
|
||||
'page_link' => function (string $url, bool $checkActive = false) {
|
||||
'page_link' => function (string $url, bool $checkActive = false) {
|
||||
echo 'href="'. $url . '" hx-get="' . $url . '"';
|
||||
if ($checkActive && str_starts_with($_SERVER['REQUEST_URI'], $url)) {
|
||||
echo ' class="is-active-route"';
|
||||
@ -18,31 +19,26 @@ app()->template->config('params', [
|
||||
'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)) {
|
||||
$params = [];
|
||||
}
|
||||
$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));
|
||||
global $stdOut;
|
||||
fwrite($stdOut, $msg . "\n");
|
||||
}
|
||||
|
||||
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();
|
||||
|
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>
|
||||
<title><?php echo htmlentities($pageTitle); ?> « myPrayerJournal</title><?php
|
||||
<title><?php echo $pageTitle; ?> « myPrayerJournal</title><?php
|
||||
if (!$isHtmx) { ?>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<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>
|
||||
</a>
|
||||
<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('/requests/active', true); ?>>Active</a></li><?php
|
||||
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 ]); ?>
|
||||
<body>
|
||||
<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; ?>
|
||||
</section>
|
||||
<?php echo app()->template->render('layout/_foot', [ 'isHtmx' => $isHtmx ]); ?>
|
||||
|
Loading…
Reference in New Issue
Block a user