Skip to content

Commit

Permalink
Code style, dark mode, and navbar in own template [SLE-192]
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelgfeller committed Jan 22, 2024
1 parent 68ffe8c commit eda0627
Show file tree
Hide file tree
Showing 46 changed files with 178 additions and 139 deletions.
2 changes: 1 addition & 1 deletion .cs.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
'array_syntax' => ['syntax' => 'short'],
'cast_spaces' => ['space' => 'none'],
'concat_space' => ['spacing' => 'one'],
'compact_nullable_typehint' => true,
'compact_nullable_type_declaration' => true,
'nullable_type_declaration' => true,
'nullable_type_declaration_for_default_null_value' => true,
'declare_equal_normalize' => ['space' => 'single'],
Expand Down
21 changes: 10 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This project showcases a real-world-example of a backend and frontend built usin
[Slim](https://www.slimframework.com/) micro-framework.

The primary goal is to provide a modern codebase with a scalable project structure and
a range of practical feature implementations.
the implementation of a range of practical features.
These can serve as learning examples or be adapted for developing new
applications.

Expand All @@ -26,18 +26,18 @@ Project components:
* Authentication (login) and authorization (permissions)
* Account verification and password reset via email link and token
* Protection against rapid fire brute force and password spraying attacks (time throttling and
captcha) - [docs](https://github.com/samuelgfeller/slim-example-project/blob/master/docs/security-concept.md)
captcha)
* Localization — English, German and French
* Flash messages
* Request body and input validation
* Template rendering with native PHP syntax
* An intuitive method for editing values in the browser using "contenteditable"
* Dark theme
* Custom error handler - [docs](https://github.com/samuelgfeller/slim-example-project/blob/master/docs/error-handling.md)
* Integration testing with fixtures and data providers [docs](https://github.com/samuelgfeller/slim-example-project/blob/master/docs/testing/testing-cheatsheet.md)
* Database migrations and seeding [docs](https://github.com/samuelgfeller/slim-example-project/blob/master/docs/cheatsheet.md#database-migrations)
* Custom error handler
* Integration testing with fixtures and data providers
* Database migrations and seeding

Application components demonstrating real-world features as examples:
Application components demonstrating examples for real-world features:
* Users with 4 different roles and different permissions
* User management for administrators
* User activity history
Expand Down Expand Up @@ -95,9 +95,8 @@ The database is reset every half-hour.

**Frontend**
* [Template rendering](https://github.com/samuelgfeller/slim-example-project/wiki/Template-rendering)
* [Dark mode - (coming soon)]()
* [JS Modules - (coming soon)]()
* [Ajax - (coming soon)](https://github.com/samuelgfeller/slim-example-project/wiki/Ajax)
* [JavaScript - (coming soon)](https://github.com/samuelgfeller/slim-example-project/wiki/JavaScript)
* [Dark mode](https://github.com/samuelgfeller/slim-example-project/wiki/Dark-Mode)

**Other**
* [Directory structure](https://github.com/samuelgfeller/slim-example-project/wiki/Directory-structure)
Expand Down Expand Up @@ -157,6 +156,8 @@ so that it's long living and can be adapted to different needs and preferences.
Basically, this is my take on what a modern and efficient web app could look like with today's
tech.

## Credits

I worked closely with the software architect
[Daniel Opitz](https://odan.github.io/about.html), who also reviewed this project.
I learned a lot during
Expand All @@ -165,8 +166,6 @@ and was inspired by his books, articles, tutorials and his slim
[skeleton-project](https://github.com/odan/slim4-skeleton).
I'm grateful for his support and the time he took to help me improve this project.

## Credits

Special thanks to [JetBrains](https://jb.gg/OpenSource) for supporting this project.

## Licence
Expand Down
2 changes: 2 additions & 0 deletions config/container.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
$rotatingFileHandler = new RotatingFileHandler($filename, 0, $level, true, 0777);
// The last "true" here tells monolog to remove empty []'s
$rotatingFileHandler->setFormatter(new LineFormatter(null, 'Y-m-d H:i:s', false, true));

return $logger->pushHandler($rotatingFileHandler);
},

Expand Down Expand Up @@ -153,6 +154,7 @@

SessionInterface::class => function (ContainerInterface $container) {
$options = $container->get('settings')['session'];

return new PhpSession($options);
},

Expand Down
4 changes: 2 additions & 2 deletions config/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ function html(?string $text = null): string
* If a context is provided, it is used to replace placeholders
* in the translated string.
*
* @param string $message The message to be translated.
* @param string $message the message to be translated
* @param mixed ...$context Optional elements that should be inserted in the string with placeholders.
* The function can be called like this:
* __('The %s contains %d monkeys and %d birds.', 'tree', 5, 3);
* With the argument unpacking operator ...$context, the arguments are accessible within the function as an array.
*
* @return string The translated string.
* @return string the translated string
*/
function __(string $message, ...$context): string
{
Expand Down
15 changes: 7 additions & 8 deletions public/assets/general/dark-mode/dark-mode.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
// Get the toggle switch element
import {submitUpdate} from "../ajax/submit-update-data.js?v=0.4.0";
import {displayFlashMessage} from "../page-component/flash-message/flash-message.js?v=0.4.0";

// Get the toggle switch element
const toggleSwitch = document.querySelector('#dark-mode-toggle-checkbox');

// Retrieve the current theme from localStorage
const currentTheme = localStorage.getItem('theme') ? localStorage.getItem('theme') : null;

if (toggleSwitch) {
// Add event listener to the toggle switch for theme switching
toggleSwitch.addEventListener('change', switchTheme, false);

// Retrieve the current theme from localStorage
const currentTheme = localStorage.getItem('theme') ? localStorage.getItem('theme') : null;

// Set the theme based on the stored value from localStorage
if (currentTheme) {
// Set the data-theme attribute on the html element
Expand Down Expand Up @@ -45,10 +45,9 @@ function switchTheme(e) {
let userId = document.getElementById('user-id').value;
submitUpdate({theme: theme}, `users/${userId}`, true)
.then(r => {
})
.catch(r => {
displayFlashMessage('error', 'Failed to change the theme in the database.')
});
}).catch(r => {
displayFlashMessage('error', 'Failed to change the theme in the database.')
});
}


1 change: 0 additions & 1 deletion public/assets/general/general-css/default.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
--background-accent-3-color: #dcdcdc;
--border-accent-2-color: #d5d5d5;
--accent-color-when-dark: white;
--translucent-background: rgba(255, 255, 255, 0.8);
/* Text */
--primary-text-color: #2e3e50;
--secondary-text-color: rgba(46, 62, 80, 0.80);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ public function __construct(
* @param ServerRequest $request
* @param Response $response
*
* @return Response
* @throws \Throwable
*
* @return Response
*/
public function __invoke(ServerRequest $request, Response $response): Response
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ public function __construct(
* @param ServerRequest $request
* @param Response $response
*
* @return Response
* @throws \Throwable
*
* @return Response
*/
public function __invoke(ServerRequest $request, Response $response): Response
{
Expand Down
17 changes: 15 additions & 2 deletions src/Application/ErrorHandler/DefaultErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ public function __construct(
}

/**
* @param ServerRequestInterface $request
* @param Throwable $exception
* @param bool $displayErrorDetails
* @param bool $logErrors
* @param bool $logErrorDetails
*
* @throws Throwable
* @throws \ErrorException
*/
Expand Down Expand Up @@ -91,6 +97,7 @@ public function __invoke(
// The error-details template does not include the default layout,
// so the base path to the project root folder is required to load assets
$phpRendererAttributes['basePath'] = (new BasePathDetector($request->getServerParams()))->getBasePath();

// Render template if the template path fails, the default webserver exception is shown
return $this->phpRenderer->render($response, 'error/error-details.html.php', $phpRendererAttributes);
}
Expand All @@ -108,6 +115,7 @@ public function __invoke(
* Determine http status code.
*
* @param Throwable $exception The exception
*
* @return int The http code
*/
private function getHttpStatusCode(Throwable $exception): int
Expand Down Expand Up @@ -136,6 +144,7 @@ private function getHttpStatusCode(Throwable $exception): int
* Build the attribute array for the detailed error page.
*
* @param Throwable $exception
*
* @return array
*/
private function getExceptionDetailsAttributes(Throwable $exception): array
Expand Down Expand Up @@ -214,6 +223,7 @@ private function getExceptionDetailsAttributes(Throwable $exception): array
* This function returns the argument as a string.
*
* @param mixed $argument
*
* @return string
*/
private function getTraceArgumentAsString(mixed $argument): string
Expand All @@ -237,6 +247,7 @@ private function getTraceArgumentAsString(mixed $argument): string
$result[$key] = $value;
}
}

// Return the array converted to a string using var_export
return var_export($result, true);
}
Expand All @@ -249,8 +260,9 @@ private function getTraceArgumentAsString(mixed $argument): string
* Convert the given argument to a string not longer than 15 chars
* except if it's a file or a class name.
*
* @param mixed $argument The variable to be converted to a string.
* @return string The string representation of the variable.
* @param mixed $argument the variable to be converted to a string
*
* @return string the string representation of the variable
*/
private function getTraceArgumentAsTruncatedString(mixed $argument): string
{
Expand Down Expand Up @@ -288,6 +300,7 @@ private function getTraceArgumentAsTruncatedString(mixed $argument): string
* If a string is 'App\Domain\Example\Class', this function returns 'Class'.
*
* @param string $string
*
* @return string
*/
private function removeEverythingBeforeLastBackslash(string $string): string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ public function __construct(bool $displayErrorDetails, bool $logErrors, private
* @param ServerRequestInterface $request The request
* @param RequestHandlerInterface $handler The handler
*
* @return ResponseInterface The response
* @throws ErrorException
*
* @return ResponseInterface The response
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
Expand Down Expand Up @@ -59,9 +59,11 @@ function ($severity, $message, $file, $line) {
throw new ErrorException($message, 0, $severity, $file, $line);
}
}

return true;
}
);

return $handler->handle($request);
}
}
1 change: 0 additions & 1 deletion src/Application/Renderer/RedirectHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

readonly class RedirectHandler
{

public function __construct(private RouteParserInterface $routeParser)
{
}
Expand Down
2 changes: 1 addition & 1 deletion src/Application/Renderer/TemplateRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ public function renderOnValidationError(
* @param array|null $preloadValues
* @param array $queryParams same query params passed to page to be added again to form after validation error
*
* @return ResponseInterface
* @throws \Throwable
*
* @return ResponseInterface
*/
public function respondWithFormThrottle(
ResponseInterface $response,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public function getRoleHierarchyByUserId(int $userId): int
->leftJoin('user_role', ['user.user_role_id = user_role.id'])
->where(['user.deleted_at IS' => null, 'user.id' => $userId]);
$roleResultRow = $query->execute()->fetch('assoc');

// If no role found, return highest hierarchy which means lowest privileged role
return (int)($roleResultRow['hierarchy'] ?? 1000);
}
Expand Down
8 changes: 4 additions & 4 deletions src/Domain/Authentication/Service/LoginVerifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ public function __construct(
*
* @param array $userLoginValues An associative array containing the user's login credentials.
* Expected keys are 'email' and 'password' and optionally 'g-recaptcha-response'.
* @param array $queryParams An associative array containing any additional query parameters.
* @param array $queryParams an associative array containing any additional query parameters
*
* @throws TransportExceptionInterface If an error occurs while sending an email to a non-active user.
* @throws InvalidCredentialsException If the user does not exist or the password is incorrect.
* @throws TransportExceptionInterface if an error occurs while sending an email to a non-active user
* @throws InvalidCredentialsException if the user does not exist or the password is incorrect
*
* @return int The ID of the user if the login is successful.
* @return int the ID of the user if the login is successful
*/
public function verifyLoginAndGetUserId(array $userLoginValues, array $queryParams = []): int
{
Expand Down
1 change: 0 additions & 1 deletion src/Domain/Authorization/Privilege.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ enum Privilege
// Allowed to Read, Create, Update and Delete
case CRUD;


// Initially, the Privilege Enum was the datatype in result objects that was passed to the PHP templates.
// The case names were the name of the highest privilege (Read, Create, Update, Delete).
// The values were the letters of the associated permissions meaning Delete was 'CRUD', Update was 'CRU' and so on.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public function isGrantedToCreate(?ClientData $client = null): bool
'loggedInUserId not set while isGrantedToCreate authorization check $client: '
. json_encode($client, JSON_PARTIAL_OUTPUT_ON_ERROR)
);

return false;
}
$authenticatedUserRoleHierarchy = $this->userRoleFinderRepository->getRoleHierarchyByUserId(
Expand Down Expand Up @@ -89,6 +90,7 @@ public function isGrantedToAssignUserToClient(
'loggedInUserId not set while isGrantedToAssignUserToClient authorization check $assignedUserId: '
. $assignedUserId
);

return false;
}

Expand Down Expand Up @@ -136,6 +138,7 @@ public function isGrantedToUpdate(array $clientDataToUpdate, ?int $ownerId, bool
'loggedInUserId not set while isGrantedToUpdate authorization check $clientDataToUpdate: '
. json_encode($clientDataToUpdate, JSON_PARTIAL_OUTPUT_ON_ERROR)
);

return false;
}
$authenticatedUserRoleHierarchy = $this->userRoleFinderRepository->getRoleHierarchyByUserId(
Expand Down Expand Up @@ -217,6 +220,7 @@ public function isGrantedToDelete(?int $ownerId, bool $log = true): bool
{
if (!$this->loggedInUserId) {
$this->logger->error('loggedInUserId not set while isGrantedToDelete authorization check');

return false;
}
$authenticatedUserRoleHierarchy = $this->userRoleFinderRepository->getRoleHierarchyByUserId(
Expand Down
2 changes: 1 addition & 1 deletion src/Domain/Client/Service/ClientValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,4 @@ public function validateClientValues(array $clientCreationValues, bool $isCreate
throw new ValidationException($errors);
}
}
}
}
4 changes: 2 additions & 2 deletions src/Domain/Dashboard/Panel/UserFilterChipProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function getUserFilterChipsHtml(): string
$filters = $this->getActiveAndInactiveUserFilters();
$activeFilterChips = '';
foreach ($filters['active'] as $filterCategory => $filtersInCategory) {
/** @var \App\Domain\FilterSetting\Data\FilterData $filterData */
/** @var FilterData $filterData */
foreach ($filtersInCategory as $filterId => $filterData) {
$activeFilterChips .= "<div class='filter-chip filter-chip-active'>\n
<span data-filter-id='$filterId' data-param-name='$filterData->paramName'
Expand All @@ -44,7 +44,7 @@ public function getUserFilterChipsHtml(): string
foreach ($filters['inactive'] as $filterCategory => $filtersInCategory) {
$inactiveFilterChips .=
"<span class='filter-chip-container-label' data-category='$filterCategory'>$filterCategory</span>";
/** @var \App\Domain\FilterSetting\Data\FilterData $filterData */
/** @var FilterData $filterData */
foreach ($filtersInCategory as $filterId => $filterData) {
$inactiveFilterChips .= "<div class='filter-chip'>
<span data-filter-id='$filterId' data-param-name='$filterData->paramName'
Expand Down
1 change: 0 additions & 1 deletion src/Domain/Note/Service/NoteUpdater.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ public function updateNote(int $noteId, ?array $noteValues): bool
}
$this->userActivityLogger->logUserActivity(UserActivity::UPDATED, 'note', $noteId, $updateData);


return $updated;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public function __construct(
* @param int $seconds
* Throws PersistenceRecordNotFoundException if entry not found
* @param int|null $userId
*
* @return int
*/
public function getLoggedEmailCountInTimespan(string $email, int $seconds, ?int $userId): int
Expand Down
1 change: 1 addition & 0 deletions src/Domain/Security/Service/EmailRequestFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public function __construct(
*
* @param string $email
* @param int|null $userId
*
* @return int
*/
public function findEmailAmountInSetTimespan(string $email, ?int $userId): int
Expand Down
Loading

0 comments on commit eda0627

Please sign in to comment.