Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Select dropdowns use form-select class and not form-control #11

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
vendor
composer.lock
1 change: 1 addition & 0 deletions .htaccess
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Require all denied
39 changes: 39 additions & 0 deletions app/Bootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace App;

use Nette\Bootstrap\Configurator;


/**
* Bootstrap class initializes application environment and DI container.
*/
class Bootstrap
{
public static function boot(): Configurator
{
// The configurator is responsible for setting up the application environment and services.
// Learn more at https://doc.nette.org/en/bootstrap
$configurator = new Configurator;
$appDir = dirname(__DIR__);

// Nette is smart, and the development mode turns on automatically,
// or you can enable for a specific IP address it by uncommenting the following line:
// $configurator->setDebugMode('[email protected]');

// Enables Tracy: the ultimate "swiss army knife" debugging tool.
// Learn more about Tracy at https://tracy.nette.org
$configurator->enableTracy($appDir . '/log');

// Set the directory for temporary files generated by Nette (e.g. compiled templates)
$configurator->setTempDirectory($appDir . '/temp');

// Add configuration files
$configurator->addConfig($appDir . '/config/common.neon');
$configurator->addConfig($appDir . '/config/services.neon');

return $configurator;
}
}
25 changes: 25 additions & 0 deletions app/Core/RouterFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace App\Core;

use Nette;
use Nette\Application\Routers\RouteList;


final class RouterFactory
{
use Nette\StaticClass;

/**
* Creates the main application router with defined routes.
*/
public static function createRouter(): RouteList
{
$router = new RouteList;
// Default route that maps to the Dashboard
$router->addRoute('<presenter>/<action>', 'Dashboard:default');
return $router;
}
}
95 changes: 95 additions & 0 deletions app/Model/UserFacade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

declare(strict_types=1);

namespace App\Model;

use Nette;
use Nette\Security\Passwords;


/**
* Manages user-related operations such as authentication and adding new users.
*/
final class UserFacade implements Nette\Security\Authenticator
{
// Minimum password length requirement for users
public const PasswordMinLength = 7;

// Database table and column names
private const
TableName = 'users',
ColumnId = 'id',
ColumnName = 'username',
ColumnPasswordHash = 'password',
ColumnEmail = 'email',
ColumnRole = 'role';

// Dependency injection of database explorer and password utilities
public function __construct(
private Nette\Database\Explorer $database,
private Passwords $passwords,
) {
}


/**
* Authenticate a user based on provided credentials.
* Throws an AuthenticationException if authentication fails.
*/
public function authenticate(string $username, string $password): Nette\Security\SimpleIdentity
{
// Fetch the user details from the database by username
$row = $this->database->table(self::TableName)
->where(self::ColumnName, $username)
->fetch();

// Authentication checks
if (!$row) {
throw new Nette\Security\AuthenticationException('The username is incorrect.', self::IdentityNotFound);

} elseif (!$this->passwords->verify($password, $row[self::ColumnPasswordHash])) {
throw new Nette\Security\AuthenticationException('The password is incorrect.', self::InvalidCredential);

} elseif ($this->passwords->needsRehash($row[self::ColumnPasswordHash])) {
$row->update([
self::ColumnPasswordHash => $this->passwords->hash($password),
]);
}

// Return user identity without the password hash
$arr = $row->toArray();
unset($arr[self::ColumnPasswordHash]);
return new Nette\Security\SimpleIdentity($row[self::ColumnId], $row[self::ColumnRole], $arr);
}


/**
* Add a new user to the database.
* Throws a DuplicateNameException if the username is already taken.
*/
public function add(string $username, string $email, string $password): void
{
// Validate the email format
Nette\Utils\Validators::assert($email, 'email');

// Attempt to insert the new user into the database
try {
$this->database->table(self::TableName)->insert([
self::ColumnName => $username,
self::ColumnPasswordHash => $this->passwords->hash($password),
self::ColumnEmail => $email,
]);
} catch (Nette\Database\UniqueConstraintViolationException $e) {
throw new DuplicateNameException;
}
}
}


/**
* Custom exception for duplicate usernames.
*/
class DuplicateNameException extends \Exception
{
}
30 changes: 30 additions & 0 deletions app/UI/@layout.latte
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{import 'form-bootstrap5.latte'}

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">

{* Page title with optional prefix from the child template *}
<title>{ifset title}{include title|stripHtml} | {/ifset}User Login Example</title>

{* Link to the Bootstrap stylesheet for styling *}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>

<body>
<div class=container>
{* Flash messages display block *}
<div n:foreach="$flashes as $flash" n:class="alert, 'alert-' . $flash->type">{$flash->message}</div>

{* Main content of the child template goes here *}
{include content}
</div>

{* Scripts block; by default includes Nette Forms script for validation *}
{block scripts}
<script src="https://unpkg.com/nette-forms@3/src/assets/netteForms.js"></script>
{/block}
</body>
</html>
34 changes: 34 additions & 0 deletions app/UI/Accessory/FormFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace App\UI\Accessory;

use Nette;
use Nette\Application\UI\Form;


/**
* Factory for creating general forms with optional CSRF protection.
*/
final class FormFactory
{
// Dependency injection of the current user session
public function __construct(
private Nette\Security\User $user,
) {
}


/**
* Create a new form instance. If user is logged in, add CSRF protection.
*/
public function create(): Form
{
$form = new Form;
if ($this->user->isLoggedIn()) {
$form->addProtection();
}
return $form;
}
}
33 changes: 33 additions & 0 deletions app/UI/Accessory/RequireLoggedUser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace App\UI\Accessory;


/**
* Trait to enforce user authentication.
* Redirects unauthenticated users to the sign-in page.
*/
trait RequireLoggedUser
{
/**
* Injects the requirement for a logged-in user.
* Attaches a callback to the startup event of the presenter.
*/
public function injectRequireLoggedUser(): void
{
$this->onStartup[] = function () {
$user = $this->getUser();
// If the user isn't logged in, redirect them to the sign-in page
if ($user->isLoggedIn()) {
return;
} elseif ($user->getLogoutReason() === $user::LogoutInactivity) {
$this->flashMessage('You have been signed out due to inactivity. Please sign in again.');
$this->redirect('Sign:in', ['backlink' => $this->storeRequest()]);
} else {
$this->redirect('Sign:in');
}
};
}
}
19 changes: 19 additions & 0 deletions app/UI/Dashboard/DashboardPresenter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace App\UI\Dashboard;

use App\UI\Accessory\RequireLoggedUser;
use Nette;


/**
* Presenter for the dashboard view.
* Ensures the user is logged in before access.
*/
final class DashboardPresenter extends Nette\Application\UI\Presenter
{
// Incorporates methods to check user login status
use RequireLoggedUser;
}
6 changes: 6 additions & 0 deletions app/UI/Dashboard/default.latte
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{block content}
<h1>Dashboard</h1>

<p>If you see this page, it means you have successfully logged in.</p>

<p>(<a n:href="Sign:out">Sign out</a>)</p>
Loading