diff --git a/Makefile b/Makefile index 4d684905..58c203fa 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,7 @@ app_database_rollback: make exec_app_cmd CMD="php bin/console.php database:migration:rollback" app_user_create_test: - make exec_app_cmd CMD="php bin/console.php user:create a@a a" + make exec_app_cmd CMD="php bin/console.php user:create a@a aaaaaaaa a" app_sync_trakt: make exec_app_cmd CMD="php bin/console.php trakt:sync --overwrite --userId=1" diff --git a/README.md b/README.md index e847d0aa..431d2cec 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ volumes: ## Important: First steps - Run database migrations: `docker exec movary php bin/console.php database:migration:migrate` -- Create a user: `docker exec movary php bin/console.php user:create example@email.com your-password` +- Create a user: `docker exec movary php bin/console.php user:create email@example.com password username` List all available cli commands: `docker exec movary php bin/console.php` @@ -188,7 +188,7 @@ Example cli sync (import history and ratings for user with id 1): Use if you want to overwrite the local state with the trakt state (deletes and overwrites local data) - `--ignore-cache` Use if you want to sync everything from trakt regardless if there was a change since the last sync. - + ### Letterboxd.com Import diff --git a/db/migrations/20220725113013_UpdateUsername.php b/db/migrations/20220725113013_UpdateUsername.php new file mode 100644 index 00000000..68c9532f --- /dev/null +++ b/db/migrations/20220725113013_UpdateUsername.php @@ -0,0 +1,25 @@ +execute( + <<execute( + <<addRoute( 'GET', - '/{userId:\d+}/dashboard', + '/{username:.+}/dashboard', [\Movary\HttpController\DashboardController::class, 'render'] ); $routeCollector->addRoute( @@ -39,7 +39,7 @@ ); $routeCollector->addRoute( 'GET', - '/{userId:\d+}/history', + '/{username:.+}/history', [\Movary\HttpController\HistoryController::class, 'renderHistory'] ); $routeCollector->addRoute( @@ -69,12 +69,12 @@ ); $routeCollector->addRoute( 'GET', - '/{userId:\d+}/most-watched-actors', + '/{username:.+}/most-watched-actors', [\Movary\HttpController\MostWatchedActorsController::class, 'renderPage'] ); $routeCollector->addRoute( 'GET', - '/{userId:\d+}/most-watched-directors', + '/{username:.+}/most-watched-directors', [\Movary\HttpController\MostWatchedDirectorsController::class, 'renderPage'] ); $routeCollector->addRoute( @@ -94,7 +94,7 @@ ); $routeCollector->addRoute( 'GET', - '/{userId:\d+}/movie/{id:\d+}', + '/{username:.+}/movie/{id:\d+}', [\Movary\HttpController\MovieController::class, 'renderPage'] ); $routeCollector->addRoute( @@ -109,7 +109,7 @@ ); $routeCollector->addRoute( 'GET', - '/{userId:\d+}/person/{id:\d+}', + '/{username:.+}/person/{id:\d+}', [\Movary\HttpController\PersonController::class, 'renderPage'] ); $routeCollector->addRoute( @@ -132,6 +132,11 @@ '/user/password', [\Movary\HttpController\SettingsController::class, 'updatePassword'] ); + $routeCollector->addRoute( + 'POST', + '/user/name', + [\Movary\HttpController\SettingsController::class, 'updateName'] + ); $routeCollector->addRoute( 'GET', '/user/delete-ratings', diff --git a/src/Application/User/Api.php b/src/Application/User/Api.php index 32262320..92816436 100644 --- a/src/Application/User/Api.php +++ b/src/Application/User/Api.php @@ -2,19 +2,24 @@ namespace Movary\Application\User; -use Movary\Application\User\Exception\PasswordTooShort; +use Movary\Application\User\Service\Validator; use Ramsey\Uuid\Uuid; class Api { - private const PASSWORD_MIN_LENGTH = 8; - - public function __construct(private readonly Repository $repository) - { + public function __construct( + private readonly Repository $repository, + private readonly Validator $userValidator + ) { } - public function createUser(string $email, string $password, ?string $name) : void + public function createUser(string $email, string $password, string $name) : void { + $this->userValidator->ensureEmailIsUnique($email); + $this->userValidator->ensurePasswordIsValid($password); + $this->userValidator->ensureNameFormatIsValid($name); + $this->userValidator->ensureNameIsUnique($name); + $this->repository->createUser($email, password_hash($password, PASSWORD_DEFAULT), $name); } @@ -70,6 +75,11 @@ public function findTraktUserName(int $userId) : ?string return $this->repository->findTraktUserName($userId); } + public function findUserByName(string $name) : ?Entity + { + return $this->repository->findUserByName($name); + } + public function findUserIdByPlexWebhookId(string $webhookId) : ?int { return $this->repository->findUserIdByPlexWebhookId($webhookId); @@ -107,14 +117,22 @@ public function updateDateFormatId(int $userId, int $dateFormat) : void public function updateEmail(int $userId, string $email) : void { + $this->userValidator->ensureEmailIsUnique($email, $userId); + $this->repository->updateEmail($userId, $email); } + public function updateName(int $userId, string $name) : void + { + $this->userValidator->ensureNameFormatIsValid($name); + $this->userValidator->ensureNameIsUnique($name, $userId); + + $this->repository->updateName($userId, $name); + } + public function updatePassword(int $userId, string $newPassword) : void { - if (strlen($newPassword) < self::PASSWORD_MIN_LENGTH) { - throw new PasswordTooShort(self::PASSWORD_MIN_LENGTH); - } + $this->userValidator->ensurePasswordIsValid($newPassword); if ($this->repository->findUserById($userId) === null) { throw new \RuntimeException('There is no user with id: ' . $userId); diff --git a/src/Application/User/Entity.php b/src/Application/User/Entity.php index 33943dc5..a5ae8b10 100644 --- a/src/Application/User/Entity.php +++ b/src/Application/User/Entity.php @@ -6,10 +6,11 @@ class Entity { private function __construct( private readonly int $id, + private readonly string $name, private readonly string $passwordHash, private readonly bool $areCoreAccountChangesDisabled, + private readonly int $dateFormatId, private readonly ?string $plexWebhookUuid, - private readonly ?int $dateFormatId, private readonly ?string $TraktUserName, private readonly ?string $TraktClientId, ) { @@ -19,10 +20,11 @@ public static function createFromArray(array $data) : self { return new self( (int)$data['id'], + $data['name'], $data['password'], (bool)$data['core_account_changes_disabled'], - $data['plex_webhook_uuid'], $data['date_format_id'], + $data['plex_webhook_uuid'], $data['trakt_user_name'], $data['trakt_client_id'], ); @@ -33,7 +35,7 @@ public function areCoreAccountChangesDisabled() : bool return $this->areCoreAccountChangesDisabled; } - public function getDateFormatId() : ?int + public function getDateFormatId() : int { return $this->dateFormatId; } @@ -43,6 +45,11 @@ public function getId() : int return $this->id; } + public function getName() : string + { + return $this->name; + } + public function getPasswordHash() : string { return $this->passwordHash; diff --git a/src/Application/User/Exception/EmailNotUnique.php b/src/Application/User/Exception/EmailNotUnique.php new file mode 100644 index 00000000..31028b24 --- /dev/null +++ b/src/Application/User/Exception/EmailNotUnique.php @@ -0,0 +1,11 @@ +dbConnection->insert( 'user', @@ -132,6 +132,17 @@ public function findUserById(int $userId) : ?Entity return Entity::createFromArray($data); } + public function findUserByName(string $name) : ?Entity + { + $data = $this->dbConnection->fetchAssociative('SELECT * FROM `user` WHERE `name` = ?', [$name]); + + if (empty($data) === true) { + return null; + } + + return Entity::createFromArray($data); + } + public function findUserIdByAuthToken(string $token) : ?int { $id = $this->dbConnection->fetchOne('SELECT `user_id` FROM `user_auth_token` WHERE `token` = ?', [$token]); @@ -206,6 +217,19 @@ public function updateEmail(int $userId, string $email) : void ); } + public function updateName(int $userId, string $name) : void + { + $this->dbConnection->update( + 'user', + [ + 'name' => $name, + ], + [ + 'id' => $userId, + ] + ); + } + public function updatePassword(int $userId, string $passwordHash) : void { $this->dbConnection->update( diff --git a/src/Application/User/Service/Authentication.php b/src/Application/User/Service/Authentication.php index 6e734770..aa27dad0 100644 --- a/src/Application/User/Service/Authentication.php +++ b/src/Application/User/Service/Authentication.php @@ -3,6 +3,7 @@ namespace Movary\Application\User\Service; use Movary\Application\User\Api; +use Movary\Application\User\Entity; use Movary\Application\User\Exception\EmailNotFound; use Movary\Application\User\Exception\InvalidPassword; use Movary\Application\User\Repository; @@ -23,6 +24,11 @@ public function deleteToken(string $token) : void $this->repository->deleteAuthToken($token); } + public function getCurrentUser() : Entity + { + return $this->userApi->fetchUser($this->getCurrentUserId()); + } + public function getCurrentUserId() : int { $userId = $_SESSION['userId'] ?? null; diff --git a/src/Application/User/Service/Validator.php b/src/Application/User/Service/Validator.php new file mode 100644 index 00000000..e47459ec --- /dev/null +++ b/src/Application/User/Service/Validator.php @@ -0,0 +1,57 @@ +repository->findUserByEmail($email); + if ($user === null) { + return; + } + + if ($user->getId() !== $expectUserId) { + throw new EmailNotUnique(); + } + } + + public function ensureNameFormatIsValid(string $name) : void + { + preg_match('~^[a-zA-Z0-9]+$~', $name, $matches); + if (empty($matches) === true) { + throw new UsernameInvalidFormat(); + } + } + + public function ensureNameIsUnique(string $name, ?int $expectUserId = null) : void + { + $user = $this->repository->findUserByName($name); + if ($user === null) { + return; + } + + if ($user->getId() !== $expectUserId) { + throw new UsernameNotUnique(); + } + } + + public function ensurePasswordIsValid(string $password) : void + { + if (strlen($password) < self::PASSWORD_MIN_LENGTH) { + throw new PasswordTooShort(self::PASSWORD_MIN_LENGTH); + } + } +} diff --git a/src/Command/UserCreate.php b/src/Command/UserCreate.php index 0fa2936d..5f550733 100644 --- a/src/Command/UserCreate.php +++ b/src/Command/UserCreate.php @@ -3,6 +3,10 @@ namespace Movary\Command; use Movary\Application\User\Api; +use Movary\Application\User\Exception\EmailNotUnique; +use Movary\Application\User\Exception\PasswordTooShort; +use Movary\Application\User\Exception\UsernameInvalidFormat; +use Movary\Application\User\Exception\UsernameNotUnique; use Psr\Log\LoggerInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -25,7 +29,7 @@ protected function configure() : void ->setDescription('Create a new user.') ->addArgument('email', InputArgument::REQUIRED, 'Email address for user') ->addArgument('password', InputArgument::REQUIRED, 'Password for user') - ->addArgument('name', InputArgument::OPTIONAL, 'Name for user'); + ->addArgument('name', InputArgument::REQUIRED, 'Name for user'); } // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter @@ -37,6 +41,22 @@ protected function execute(InputInterface $input, OutputInterface $output) : int try { $this->userApi->createUser($email, $password, $name); + } catch (EmailNotUnique $e) { + $this->generateOutput($output, 'Could not create user: Email already in use'); + + return Command::FAILURE; + } catch (PasswordTooShort $e) { + $this->generateOutput($output, 'Could not create user: Password must contain at least 8 characters'); + + return Command::FAILURE; + } catch (UsernameInvalidFormat $e) { + $this->generateOutput($output, 'Could not create user: Name must only consist of numbers and letters'); + + return Command::FAILURE; + } catch (UsernameNotUnique $e) { + $this->generateOutput($output, 'Could not create user: Name already in use'); + + return Command::FAILURE; } catch (\Throwable $t) { $this->logger->error('Could not create user.', ['exception' => $t]); diff --git a/src/Command/UserUpdate.php b/src/Command/UserUpdate.php index f3d2f60d..ebb78c84 100644 --- a/src/Command/UserUpdate.php +++ b/src/Command/UserUpdate.php @@ -3,6 +3,10 @@ namespace Movary\Command; use Movary\Application\User; +use Movary\Application\User\Exception\EmailNotUnique; +use Movary\Application\User\Exception\PasswordTooShort; +use Movary\Application\User\Exception\UsernameInvalidFormat; +use Movary\Application\User\Exception\UsernameNotUnique; use Psr\Log\LoggerInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -26,6 +30,7 @@ protected function configure() : void ->setDescription('Update user data.') ->addArgument('userId', InputArgument::REQUIRED, 'ID of user') ->addOption('email', [], InputOption::VALUE_OPTIONAL, 'New email') + ->addOption('name', [], InputOption::VALUE_OPTIONAL, 'New name') ->addOption('password', [], InputOption::VALUE_OPTIONAL, 'New password') ->addOption('coreAccountChangesDisabled', [], InputOption::VALUE_OPTIONAL, 'Set core account changes disabled status') ->addOption('traktUserName', [], InputOption::VALUE_OPTIONAL, 'New trakt user name') @@ -33,6 +38,7 @@ protected function configure() : void } // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter + // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh protected function execute(InputInterface $input, OutputInterface $output) : int { $userId = (int)$input->getArgument('userId'); @@ -43,6 +49,11 @@ protected function execute(InputInterface $input, OutputInterface $output) : int $this->userApi->updateEmail($userId, $email); } + $name = $input->getOption('name'); + if ($name !== null) { + $this->userApi->updateName($userId, $name); + } + $password = $input->getOption('password'); if ($password !== null) { $this->userApi->updatePassword($userId, $password); @@ -64,11 +75,22 @@ protected function execute(InputInterface $input, OutputInterface $output) : int $coreAccountChangesDisabled = $input->getOption('coreAccountChangesDisabled'); if ($coreAccountChangesDisabled !== null) { - $this->userApi->updateCoreAccountChangesDisabled($userId, (bool)$coreAccountChangesDisabled); } - } catch (User\Exception\PasswordTooShort $t) { - $this->generateOutput($output, "Error: Password must be at least {$t->getMinLength()} characters long."); + } catch (EmailNotUnique $e) { + $this->generateOutput($output, 'Could not update user: Email already in use'); + + return Command::FAILURE; + } catch (PasswordTooShort $e) { + $this->generateOutput($output, 'Could not update user: Password must contain at least 8 characters'); + + return Command::FAILURE; + } catch (UsernameInvalidFormat $e) { + $this->generateOutput($output, 'Could not update user: Name must only consist of numbers and letters'); + + return Command::FAILURE; + } catch (UsernameNotUnique $e) { + $this->generateOutput($output, 'Could not update user: Name already in use'); return Command::FAILURE; } catch (\Throwable $t) { diff --git a/src/Factory.php b/src/Factory.php index aa787785..68466b7e 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -140,25 +140,26 @@ public static function createTwigEnvironment(ContainerInterface $container) : Tw $twig = new Twig\Environment($container->get(Twig\Loader\LoaderInterface::class)); $currentRequest = $container->get(Request::class); - $routeUserId = $currentRequest->getRouteParameters()['userId'] ?? null; + $routeUsername = $currentRequest->getRouteParameters()['username'] ?? null; $userAuthenticated = $container->get(Authentication::class)->isUserAuthenticated(); $twig->addGlobal('loggedIn', $userAuthenticated); - $currentUserId = null; + $user = null; $dateFormatPhp = DateFormat::getPhpDefault(); $dataFormatJavascript = DateFormat::getJavascriptDefault(); if ($userAuthenticated === true) { $currentUserId = $container->get(Authentication::class)->getCurrentUserId(); - $userDateFormat = $container->get(User\Api::class)->fetchDateFormatId($currentUserId); + /** @var User\Entity $user */ + $user = $container->get(User\Api::class)->fetchUser($currentUserId); - $dateFormatPhp = DateFormat::getPhpById($userDateFormat); - $dataFormatJavascript = DateFormat::getJavascriptById($userDateFormat); + $dateFormatPhp = DateFormat::getPhpById($user->getDateFormatId()); + $dataFormatJavascript = DateFormat::getJavascriptById($user->getDateFormatId()); } - $twig->addGlobal('routeUserId', $routeUserId ?? $currentUserId); + $twig->addGlobal('routeUsername', $routeUsername ?? $user?->getName()); $twig->addGlobal('dateFormatPhp', $dateFormatPhp); $twig->addGlobal('dateFormatJavascript', $dataFormatJavascript); $twig->addGlobal('requestUrlPath', self::createCurrentHttpRequest()->getPath()); diff --git a/src/HttpController/DashboardController.php b/src/HttpController/DashboardController.php index b32f75b6..d388f920 100644 --- a/src/HttpController/DashboardController.php +++ b/src/HttpController/DashboardController.php @@ -17,12 +17,16 @@ public function __construct( private readonly Environment $twig, private readonly Select $movieHistorySelectService, private readonly Movie\Api $movieApi, + private readonly User\Api $userApi, ) { } public function render(Request $request) : Response { - $userId = (int)$request->getRouteParameters()['userId']; + $userId = $this->userApi->findUserByName((string)$request->getRouteParameters()['username'])?->getId(); + if ($userId === null) { + return Response::createNotFound(); + } return Response::create( StatusCode::createOk(), diff --git a/src/HttpController/HistoryController.php b/src/HttpController/HistoryController.php index c072f122..458faf29 100644 --- a/src/HttpController/HistoryController.php +++ b/src/HttpController/HistoryController.php @@ -6,6 +6,7 @@ use Movary\Application\Movie; use Movary\Application\Movie\History\Service\Select; use Movary\Application\Service\Tmdb\SyncMovie; +use Movary\Application\User; use Movary\Application\User\Service\Authentication; use Movary\Util\Json; use Movary\ValueObject\Date; @@ -26,7 +27,8 @@ public function __construct( private readonly Tmdb\Api $tmdbApi, private readonly Movie\Api $movieApi, private readonly SyncMovie $tmdbMovieSyncService, - private readonly Authentication $authenticationService + private readonly Authentication $authenticationService, + private readonly User\Api $userApi ) { } @@ -81,7 +83,10 @@ public function logMovie(Request $request) : Response public function renderHistory(Request $request) : Response { - $userId = (int)$request->getRouteParameters()['userId']; + $userId = $this->userApi->findUserByName((string)$request->getRouteParameters()['username'])?->getId(); + if ($userId === null) { + return Response::createNotFound(); + } $searchTerm = $request->getGetParameters()['s'] ?? null; $page = $request->getGetParameters()['p'] ?? 1; diff --git a/src/HttpController/LandingPageController.php b/src/HttpController/LandingPageController.php index 0e3a430e..f2c50bd9 100644 --- a/src/HttpController/LandingPageController.php +++ b/src/HttpController/LandingPageController.php @@ -18,9 +18,9 @@ public function __construct( public function render() : Response { if ($this->authenticationService->isUserAuthenticated() === true) { - $userId = $this->authenticationService->getCurrentUserId(); + $userName = $this->authenticationService->getCurrentUser()->getName(); - return Response::createFoundRedirect("/$userId/dashboard"); + return Response::createFoundRedirect("/$userName/dashboard"); } $failedLogin = $_SESSION['failedLogin'] ?? null; diff --git a/src/HttpController/MostWatchedActorsController.php b/src/HttpController/MostWatchedActorsController.php index fc929b75..7a997849 100644 --- a/src/HttpController/MostWatchedActorsController.php +++ b/src/HttpController/MostWatchedActorsController.php @@ -3,9 +3,7 @@ namespace Movary\HttpController; use Movary\Application\Movie\History\Service\Select; -use Movary\Application\User\Service\Authentication; -use Movary\Util\Json; -use Movary\ValueObject\Http\Header; +use Movary\Application\User\Api; use Movary\ValueObject\Http\Request; use Movary\ValueObject\Http\Response; use Movary\ValueObject\Http\StatusCode; @@ -18,12 +16,17 @@ class MostWatchedActorsController public function __construct( private readonly Select $movieHistorySelectService, private readonly Environment $twig, + private readonly Api $userApi, ) { } public function renderPage(Request $request) : Response { - $userId = (int)$request->getRouteParameters()['userId']; + $userId = $this->userApi->findUserByName((string)$request->getRouteParameters()['username'])?->getId(); + if ($userId === null) { + return Response::createNotFound(); + } + $searchTerm = $request->getGetParameters()['s'] ?? null; $page = $request->getGetParameters()['p'] ?? 1; $limit = self::DEFAULT_LIMIT; diff --git a/src/HttpController/MostWatchedDirectorsController.php b/src/HttpController/MostWatchedDirectorsController.php index 8b41e685..651e94f5 100644 --- a/src/HttpController/MostWatchedDirectorsController.php +++ b/src/HttpController/MostWatchedDirectorsController.php @@ -3,6 +3,7 @@ namespace Movary\HttpController; use Movary\Application\Movie\History\Service\Select; +use Movary\Application\User\Api; use Movary\ValueObject\Http\Request; use Movary\ValueObject\Http\Response; use Movary\ValueObject\Http\StatusCode; @@ -14,13 +15,18 @@ class MostWatchedDirectorsController public function __construct( private readonly Select $movieHistorySelectService, - private readonly Environment $twig + private readonly Environment $twig, + private readonly Api $userApi, ) { } public function renderPage(Request $request) : Response { - $userId = (int)$request->getRouteParameters()['userId']; + $userId = $this->userApi->findUserByName((string)$request->getRouteParameters()['username'])?->getId(); + if ($userId === null) { + return Response::createNotFound(); + } + $searchTerm = $request->getGetParameters()['s'] ?? null; $page = $request->getGetParameters()['p'] ?? 1; $limit = self::DEFAULT_LIMIT; diff --git a/src/HttpController/MovieController.php b/src/HttpController/MovieController.php index 75bc66c7..8f67268c 100644 --- a/src/HttpController/MovieController.php +++ b/src/HttpController/MovieController.php @@ -3,6 +3,7 @@ namespace Movary\HttpController; use Movary\Application\Movie; +use Movary\Application\User; use Movary\Application\User\Service\Authentication; use Movary\Util\Json; use Movary\ValueObject\Http\Request; @@ -16,7 +17,8 @@ class MovieController public function __construct( private readonly Environment $twig, private readonly Movie\Api $movieApi, - private readonly Authentication $authenticationService + private readonly Authentication $authenticationService, + private readonly User\Api $userApi, ) { } @@ -43,7 +45,11 @@ public function fetchMovieRatingByTmdbdId(Request $request) : Response public function renderPage(Request $request) : Response { - $userId = (int)$request->getRouteParameters()['userId']; + $userId = $this->userApi->findUserByName((string)$request->getRouteParameters()['username'])?->getId(); + if ($userId === null) { + return Response::createNotFound(); + } + $movieId = (int)$request->getRouteParameters()['id']; $movie = $this->movieApi->findById($movieId); diff --git a/src/HttpController/SettingsController.php b/src/HttpController/SettingsController.php index 95ddb265..e33f69b6 100644 --- a/src/HttpController/SettingsController.php +++ b/src/HttpController/SettingsController.php @@ -104,6 +104,8 @@ public function renderAccountPage() : Response $deletedUserHistory = empty($_SESSION['deletedUserHistory']) === false ? $_SESSION['deletedUserHistory'] : null; $deletedUserRatings = empty($_SESSION['deletedUserRatings']) === false ? $_SESSION['deletedUserRatings'] : null; $dateFormatUpdated = empty($_SESSION['dateFormatUpdated']) === false ? $_SESSION['dateFormatUpdated'] : null; + $usernameUpdated = empty($_SESSION['usernameUpdated']) === false ? $_SESSION['usernameUpdated'] : null; + $usernameErrorInvalidFormat = empty($_SESSION['usernameErrorInvalidFormat']) === false ? $_SESSION['usernameErrorInvalidFormat'] : null; unset( $_SESSION['passwordUpdated'], $_SESSION['passwordErrorCurrentInvalid'], @@ -115,6 +117,8 @@ public function renderAccountPage() : Response $_SESSION['deletedUserHistory'], $_SESSION['deletedUserRatings'], $_SESSION['dateFormatUpdated'], + $_SESSION['usernameUpdated'], + $_SESSION['usernameErrorInvalidFormat'], ); $user = $this->userApi->fetchUser($userId); @@ -126,6 +130,8 @@ public function renderAccountPage() : Response 'dateFormats' => DateFormat::getFormats(), 'dateFormatSelected' => $user->getDateFormatId(), 'dateFormatUpdated' => $dateFormatUpdated, + 'usernameUpdated' => $usernameUpdated, + 'usernameErrorInvalidFormat' => $usernameErrorInvalidFormat, 'plexWebhookUrl' => $user->getPlexWebhookId() ?? '-', 'passwordErrorNotEqual' => $passwordErrorNotEqual, 'passwordErrorMinLength' => $passwordErrorMinLength, @@ -138,6 +144,7 @@ public function renderAccountPage() : Response 'deletedUserRatings' => $deletedUserRatings, 'traktClientId' => $user->getTraktClientId(), 'traktUserName' => $user->getTraktUserName(), + 'username' => $user->getName(), 'applicationVersion' => $this->applicationVersion ?? '-', 'lastSyncTmdb' => $this->syncLogRepository->findLastTmdbSync() ?? '-', ]), @@ -255,6 +262,32 @@ public function updateDateFormatId(Request $request) : Response ); } + public function updateName(Request $request) : Response + { + if ($this->authenticationService->isUserAuthenticated() === false) { + return Response::createFoundRedirect('/'); + } + + $name = $request->getPostParameters()['username'] ?? null; + if ($name === '' || $name === null) { + throw new \RuntimeException('Invalid username: ' . $name); + } + + try { + $this->userApi->updateName($this->authenticationService->getCurrentUserId(), (string)$name); + + $_SESSION['usernameUpdated'] = true; + } catch (User\Exception\UsernameInvalidFormat $e) { + $_SESSION['usernameErrorInvalidFormat'] = true; + } + + return Response::create( + StatusCode::createSeeOther(), + null, + [Header::createLocation($_SERVER['HTTP_REFERER'])] + ); + } + public function updatePassword(Request $request) : Response { if ($this->authenticationService->isUserAuthenticated() === false) { diff --git a/templates/component/navbar.html.twig b/templates/component/navbar.html.twig index a39be153..c4a8f431 100644 --- a/templates/component/navbar.html.twig +++ b/templates/component/navbar.html.twig @@ -12,12 +12,13 @@ {% endif %} -
  • History
  • +
  • Dashboard
  • -
  • Top Actors
  • -
  • Top Directors
  • +
  • History
  • +
  • Top Actors
  • +
  • Top Directors
  • diff --git a/templates/component/settings-nav.html.twig b/templates/component/settings-nav.html.twig index d1b03c7e..3519a22b 100644 --- a/templates/component/settings-nav.html.twig +++ b/templates/component/settings-nav.html.twig @@ -14,9 +14,6 @@ - {# #} -
    +
    diff --git a/templates/page/actor.html.twig b/templates/page/actor.html.twig index 8e6360be..edb86e53 100644 --- a/templates/page/actor.html.twig +++ b/templates/page/actor.html.twig @@ -48,7 +48,7 @@
    {% for movieAsActor in moviesAsActor %}
    -
    +
    ... @@ -70,7 +70,7 @@
    {% for movieAsDirector in moviesAsDirector %}
    -
    +
    ... diff --git a/templates/page/dashboard.html.twig b/templates/page/dashboard.html.twig index d8f7c9c9..a30388c9 100644 --- a/templates/page/dashboard.html.twig +++ b/templates/page/dashboard.html.twig @@ -50,7 +50,7 @@
    {% for lastPlay in lastPlays %}
    -
    +
    ... @@ -69,7 +69,7 @@
    {% endfor %}
    - more + more
  • @@ -80,7 +80,7 @@
    {% for mostWatchedActor in mostWatchedActors %}
    -
    +
    {{ mostWatchedActor.name }}
    @@ -94,7 +94,7 @@
    {% endfor %}
    - more
  • @@ -107,7 +107,7 @@
    {% for mostWatchedActress in mostWatchedActresses %}
    -
    {{ mostWatchedActress.name }} @@ -122,7 +122,7 @@
    {% endfor %}
    - more @@ -134,7 +134,7 @@
    {% for mostWatchedDirector in mostWatchedDirectors %}
    -
    {{ mostWatchedDirector.name }} @@ -148,7 +148,7 @@
    {% endfor %} - more
    diff --git a/templates/page/history.html.twig b/templates/page/history.html.twig index 258cd8dc..6a4a75a8 100644 --- a/templates/page/history.html.twig +++ b/templates/page/history.html.twig @@ -15,7 +15,7 @@ {{ include('component/navbar.html.twig') }}
    -
    +
    @@ -23,7 +23,7 @@
    {% for historyEntry in historyEntries %} -
    +
    ...