From 562560dc1b16ea0542f8760c8e47ac7bee627047 Mon Sep 17 00:00:00 2001 From: Lee Peuker Date: Sun, 1 Sep 2024 12:02:59 +0200 Subject: [PATCH 1/4] Add basic UI to toggle location feature --- public/js/settings-account-location.js | 43 ++++++++++++++++++- .../page/settings-account-locations.html.twig | 9 ++-- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/public/js/settings-account-location.js b/public/js/settings-account-location.js index ce0ca106..72654c56 100644 --- a/public/js/settings-account-location.js +++ b/public/js/settings-account-location.js @@ -5,8 +5,13 @@ const rows = table.getElementsByTagName('tr'); reloadTable() -async function reloadTable() { +async function reloadTable(featureIsEnabled = true) { table.getElementsByTagName('tbody')[0].innerHTML = '' + + if (featureIsEnabled === false) { + return + } + document.getElementById('locationsTableLoadingSpinner').classList.remove('d-none') const response = await fetch('/settings/locations'); @@ -189,4 +194,38 @@ document.getElementById('updateLocationButton').addEventListener('click', async reloadTable() locationModal.hide() -}) \ No newline at end of file +}) + +function disableLocationFeature() { + let featureIsDisabled = document.getElementById('toggleLocationsFeatureBtn').textContent === 'Enable locations' + setLocationFeatureBtnState(!featureIsDisabled) + setLocationTableState(featureIsDisabled) + reloadTable(featureIsDisabled) + + setLocationsAlert('Locations ' + (featureIsDisabled === true ? 'enabled' : 'disabled')) +} + +function setLocationFeatureBtnState(featureIsEnabled) { + if (featureIsEnabled === true) { + document.getElementById('toggleLocationsFeatureBtn').classList.add('btn-primary') + document.getElementById('toggleLocationsFeatureBtn').classList.remove('btn-outline-danger') + document.getElementById('toggleLocationsFeatureBtn').textContent = 'Enable locations' + + return + } + + document.getElementById('toggleLocationsFeatureBtn').classList.add('btn-outline-danger') + document.getElementById('toggleLocationsFeatureBtn').classList.remove('btn-primary') + document.getElementById('toggleLocationsFeatureBtn').textContent = 'Disable locations' + +} + +function setLocationTableState(featureIsEnabled) { + if (featureIsEnabled === true) { + document.getElementById('createLocationBtn').disabled = false + + return + } + + document.getElementById('createLocationBtn').disabled = true +} \ No newline at end of file diff --git a/templates/page/settings-account-locations.html.twig b/templates/page/settings-account-locations.html.twig index 352e67d4..f6d92eb6 100644 --- a/templates/page/settings-account-locations.html.twig +++ b/templates/page/settings-account-locations.html.twig @@ -23,7 +23,7 @@
Locations
-

Customize your available location options.

+
@@ -32,7 +32,7 @@ - + @@ -41,11 +41,12 @@
Loading...
+ + +
- - {{ include('component/modal-location.html.twig') }} {% endblock %} From e701e2a74aaeef038bcb6c9fe657240606aa73db Mon Sep 17 00:00:00 2001 From: Lee Peuker Date: Sun, 1 Sep 2024 12:25:41 +0200 Subject: [PATCH 2/4] Add basic get and post endpoints for feature toggle --- ...73031_AddLocationToUserWatchDatesTable.php | 2 +- ...349_AddLocationsFeatureFlagToUserTable.php | 24 ++ ...349_AddLocationsFeatureFlagToUserTable.php | 218 ++++++++++++++++++ settings/routes.php | 2 + src/Domain/User/UserApi.php | 10 + src/Domain/User/UserRepository.php | 23 ++ src/HttpController/Web/LocationController.php | 28 +++ 7 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 db/migrations/mysql/20240901100349_AddLocationsFeatureFlagToUserTable.php create mode 100644 db/migrations/sqlite/20240901100349_AddLocationsFeatureFlagToUserTable.php diff --git a/db/migrations/mysql/20240821173031_AddLocationToUserWatchDatesTable.php b/db/migrations/mysql/20240821173031_AddLocationToUserWatchDatesTable.php index a7af9b30..58df47b9 100644 --- a/db/migrations/mysql/20240821173031_AddLocationToUserWatchDatesTable.php +++ b/db/migrations/mysql/20240821173031_AddLocationToUserWatchDatesTable.php @@ -41,7 +41,7 @@ public function up() : void ALTER TABLE `movie_user_watch_dates` ADD COLUMN `location_id` INT(10) UNSIGNED DEFAULT NULL AFTER `position`, ADD CONSTRAINT `fk_movie_user_watch_dates_location_id` FOREIGN KEY (`location_id`) REFERENCES `location` (`id`) ON DELETE CASCADE; - SQL + SQL ); } } diff --git a/db/migrations/mysql/20240901100349_AddLocationsFeatureFlagToUserTable.php b/db/migrations/mysql/20240901100349_AddLocationsFeatureFlagToUserTable.php new file mode 100644 index 00000000..5751d61d --- /dev/null +++ b/db/migrations/mysql/20240901100349_AddLocationsFeatureFlagToUserTable.php @@ -0,0 +1,24 @@ +execute( + <<execute( + <<execute( + <<execute( + 'INSERT INTO `tmp_user` ( + `id`, + `email`, + `name`, + `password`, + `totp_uri`, + `is_admin`, + `dashboard_visible_rows`, + `dashboard_extended_rows`, + `dashboard_order_rows`, + `jellyfin_access_token`, + `jellyfin_user_id`, + `jellyfin_server_url`, + `privacy_level`, + `date_format_id`, + `trakt_user_name`, + `plex_webhook_uuid`, + `jellyfin_webhook_uuid`, + `emby_webhook_uuid`, + `trakt_client_id`, + `plex_client_id`, + `plex_client_temporary_code`, + `plex_access_token`, + `plex_account_id`, + `plex_server_url`, + `jellyfin_scrobble_views`, + `emby_scrobble_views`, + `plex_scrobble_views`, + `plex_scrobble_ratings`, + `radarr_feed_uuid`, + `watchlist_automatic_removal_enabled`, + `country`, + `display_character_names`, + `core_account_changes_disabled`, + `created_at` + ) SELECT + `id`, + `email`, + `name`, + `password`, + `totp_uri`, + `is_admin`, + `dashboard_visible_rows`, + `dashboard_extended_rows`, + `dashboard_order_rows`, + `jellyfin_access_token`, + `jellyfin_user_id`, + `jellyfin_server_url`, + `privacy_level`, + `date_format_id`, + `trakt_user_name`, + `plex_webhook_uuid`, + `jellyfin_webhook_uuid`, + `emby_webhook_uuid`, + `trakt_client_id`, + `plex_client_id`, + `plex_client_temporary_code`, + `plex_access_token`, + `plex_account_id`, + `plex_server_url`, + `jellyfin_scrobble_views`, + `emby_scrobble_views`, + `plex_scrobble_views`, + `plex_scrobble_ratings`, + `radarr_feed_uuid`, + `watchlist_automatic_removal_enabled`, + `country`, + `display_character_names`, + `core_account_changes_disabled`, + `created_at` FROM user', + ); + $this->execute('DROP TABLE `user`'); + $this->execute('ALTER TABLE `tmp_user` RENAME TO `user`'); + } + + public function up() : void + { + $this->execute( + <<execute( + 'INSERT INTO `tmp_user` ( + `id`, + `email`, + `name`, + `password`, + `totp_uri`, + `is_admin`, + `dashboard_visible_rows`, + `dashboard_extended_rows`, + `dashboard_order_rows`, + `jellyfin_access_token`, + `jellyfin_user_id`, + `jellyfin_server_url`, + `jellyfin_sync_enabled`, + `privacy_level`, + `date_format_id`, + `trakt_user_name`, + `plex_webhook_uuid`, + `jellyfin_webhook_uuid`, + `emby_webhook_uuid`, + `trakt_client_id`, + `plex_client_id`, + `plex_client_temporary_code`, + `plex_access_token`, + `plex_account_id`, + `plex_server_url`, + `jellyfin_scrobble_views`, + `emby_scrobble_views`, + `plex_scrobble_views`, + `plex_scrobble_ratings`, + `radarr_feed_uuid`, + `watchlist_automatic_removal_enabled`, + `country`, + `display_character_names`, + `core_account_changes_disabled`, + `created_at` + ) SELECT * FROM user', + ); + $this->execute('DROP TABLE `user`'); + $this->execute('ALTER TABLE `tmp_user` RENAME TO `user`'); + } +} diff --git a/settings/routes.php b/settings/routes.php index 6171c11f..2de22043 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -148,6 +148,8 @@ function addWebRoutes(RouterService $routerService, FastRoute\RouteCollector $ro $routes->add('POST', '/settings/locations', [Web\LocationController::class, 'createLocation'], [Web\Middleware\UserIsAuthenticated::class]); $routes->add('PUT', '/settings/locations/{locationId:\d+}', [Web\LocationController::class, 'updateLocation'], [Web\Middleware\UserIsAuthenticated::class]); $routes->add('DELETE', '/settings/locations/{locationId:\d+}', [Web\LocationController::class, 'deleteLocation'], [Web\Middleware\UserIsAuthenticated::class]); + $routes->add('GET', '/settings/locations/toggle-feature', [Web\LocationController::class, 'fetchToggleFeature'], [Web\Middleware\UserIsAuthenticated::class]); + $routes->add('POST', '/settings/locations/toggle-feature', [Web\LocationController::class, 'updateToggleFeature'], [Web\Middleware\UserIsAuthenticated::class]); $routes->add('GET', '/settings/integrations/radarr', [Web\SettingsController::class, 'renderRadarrPage'], [Web\Middleware\UserIsAuthenticated::class]); $routes->add('PUT', '/settings/radarr/feed', [RadarrController::class, 'regenerateRadarrFeedUrl'], [Web\Middleware\UserIsAuthenticated::class]); diff --git a/src/Domain/User/UserApi.php b/src/Domain/User/UserApi.php index fc31db47..8e0eeee0 100644 --- a/src/Domain/User/UserApi.php +++ b/src/Domain/User/UserApi.php @@ -259,6 +259,11 @@ public function hasUsers() : bool return $this->repository->getCountOfUsers() > 0; } + public function isLocationsEnabled(int $userId) : bool + { + return $this->repository->isLocationsEnabled($userId); + } + public function isValidPassword(int $userId, string $password) : bool { $passwordHash = $this->repository->findUserById($userId)?->getPasswordHash(); @@ -372,6 +377,11 @@ public function updateJellyfinSyncEnabled(int $userId, bool $enabledSync) : void $this->repository->updateJellyfinSyncEnabled($userId, $enabledSync); } + public function updateLocationsEnabled(int $userId, bool $locationsEnabled) : void + { + $this->repository->updateLocationsEnabled($userId, $locationsEnabled); + } + public function updateName(int $userId, string $name) : void { $this->userValidator->ensureNameFormatIsValid($name); diff --git a/src/Domain/User/UserRepository.php b/src/Domain/User/UserRepository.php index 2619867a..15f6d5df 100644 --- a/src/Domain/User/UserRepository.php +++ b/src/Domain/User/UserRepository.php @@ -448,6 +448,16 @@ public function hasHiddenPerson(int $userId, int $personId) : bool return (bool)$userPersonSettings[0]['is_hidden_in_top_lists']; } + public function isLocationsEnabled(int $userId) : bool + { + $userPersonSettings = $this->dbConnection->fetchAllAssociative( + 'SELECT locations_enabled FROM user WHERE id = ?', + [$userId], + ); + + return (bool)$userPersonSettings[0]['locations_enabled']; + } + public function setEmbyWebhookId(int $userId, ?string $embyWebhookId) : void { $this->dbConnection->update( @@ -657,6 +667,19 @@ public function updateJellyfinSyncEnabled(int $userId, bool $enabledSync) : void ); } + public function updateLocationsEnabled(int $userId, bool $locationsEnabled) : void + { + $this->dbConnection->update( + 'user', + [ + 'locations_enabled' => (int)$locationsEnabled, + ], + [ + 'id' => $userId, + ], + ); + } + public function updateName(int $userId, string $name) : void { $this->dbConnection->update( diff --git a/src/HttpController/Web/LocationController.php b/src/HttpController/Web/LocationController.php index 7fa3a42b..0ab94e91 100644 --- a/src/HttpController/Web/LocationController.php +++ b/src/HttpController/Web/LocationController.php @@ -4,6 +4,7 @@ use Movary\Domain\Movie\History\Location\MovieHistoryLocationApi; use Movary\Domain\User\Service\Authentication; +use Movary\Domain\User\UserApi; use Movary\Util\Json; use Movary\ValueObject\Http\Request; use Movary\ValueObject\Http\Response; @@ -13,6 +14,7 @@ class LocationController public function __construct( private readonly Authentication $authenticationService, private readonly MovieHistoryLocationApi $locationApi, + private readonly UserApi $userApi, ) { } @@ -58,6 +60,19 @@ public function fetchLocations() : Response return Response::createJson(Json::encode($locations)); } + public function fetchToggleFeature() : Response + { + $currentUser = $this->authenticationService->getCurrentUser(); + + $isLocationsEnabled = $this->userApi->isLocationsEnabled($currentUser->getId()); + + return Response::createJson( + Json::encode( + ['locationsEnabled' => $isLocationsEnabled], + ), + ); + } + public function updateLocation(Request $request) : Response { $currentUser = $this->authenticationService->getCurrentUser(); @@ -81,4 +96,17 @@ public function updateLocation(Request $request) : Response return Response::createOk(); } + + public function updateToggleFeature(Request $request) : Response + { + $currentUser = $this->authenticationService->getCurrentUser(); + $requestData = Json::decode($request->getBody()); + + $this->userApi->updateLocationsEnabled( + $currentUser->getId(), + $requestData['locationsEnabled'], + ); + + return Response::createNoContent(); + } } From 9dcfa7cb87b45ea08342230e651dc8b6493d1922 Mon Sep 17 00:00:00 2001 From: Lee Peuker Date: Sun, 1 Sep 2024 18:36:03 +0200 Subject: [PATCH 3/4] Use feature togggle backend in frontend --- public/js/settings-account-location.js | 43 ++++++++++++++++--- src/Domain/User/UserEntity.php | 7 +++ src/Factory.php | 2 + src/HttpController/Web/SettingsController.php | 6 ++- .../page/settings-account-locations.html.twig | 12 ++++-- 5 files changed, 59 insertions(+), 11 deletions(-) diff --git a/public/js/settings-account-location.js b/public/js/settings-account-location.js index 72654c56..3775981a 100644 --- a/public/js/settings-account-location.js +++ b/public/js/settings-account-location.js @@ -8,7 +8,7 @@ reloadTable() async function reloadTable(featureIsEnabled = true) { table.getElementsByTagName('tbody')[0].innerHTML = '' - if (featureIsEnabled === false) { + if (document.getElementById('toggleLocationsFeatureBtn').textContent === 'Enable locations') { return } @@ -196,13 +196,14 @@ document.getElementById('updateLocationButton').addEventListener('click', async locationModal.hide() }) -function disableLocationFeature() { - let featureIsDisabled = document.getElementById('toggleLocationsFeatureBtn').textContent === 'Enable locations' - setLocationFeatureBtnState(!featureIsDisabled) - setLocationTableState(featureIsDisabled) - reloadTable(featureIsDisabled) +async function toggleLocationFeature() { + let enableLocationsFeature = document.getElementById('toggleLocationsFeatureBtn').textContent === 'Enable locations' + await sendRequestToggleLocationsFeature(enableLocationsFeature) + setLocationFeatureBtnState(!enableLocationsFeature) + setLocationTableState(enableLocationsFeature) + reloadTable(enableLocationsFeature) - setLocationsAlert('Locations ' + (featureIsDisabled === true ? 'enabled' : 'disabled')) + setLocationsAlert('Locations ' + (enableLocationsFeature === true ? 'enabled' : 'disabled')) } function setLocationFeatureBtnState(featureIsEnabled) { @@ -228,4 +229,32 @@ function setLocationTableState(featureIsEnabled) { } document.getElementById('createLocationBtn').disabled = true +} + +async function sendRequestToggleLocationsFeature(isLocationsEnabled) { + const response = await fetch('/settings/locations/toggle-feature', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + 'locationsEnabled': isLocationsEnabled, + }) + }) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } +} + + +async function sendRequestFetchIsLocationsFeatureEnabled() { + const response = await fetch('/settings/locations/toggle-feature', {method: 'GET'}) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + const data = await response.json() + + return data.locationsEnabled } \ No newline at end of file diff --git a/src/Domain/User/UserEntity.php b/src/Domain/User/UserEntity.php index 0dec0f86..b7b3eadf 100644 --- a/src/Domain/User/UserEntity.php +++ b/src/Domain/User/UserEntity.php @@ -34,6 +34,7 @@ private function __construct( private readonly ?string $country, private readonly bool $jellyfinSyncEnabled, private readonly bool $displayCharacterNames, + private readonly bool $hasLocationsEnabled, ) { } @@ -66,6 +67,7 @@ public static function createFromArray(array $data) : self $data['country'], (bool)$data['jellyfin_sync_enabled'], (bool)$data['display_character_names'], + (bool)$data['locations_enabled'], ); } @@ -179,6 +181,11 @@ public function hasJellyfinSyncEnabled() : bool return $this->jellyfinSyncEnabled; } + public function hasLocationsEnabled() : bool + { + return $this->hasLocationsEnabled; + } + public function hasPlexScrobbleRatingsEnabled() : bool { return $this->plexScrobbleRatings; diff --git a/src/Factory.php b/src/Factory.php index accd8f62..782fa715 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -273,6 +273,7 @@ public static function createTraktApi(ContainerInterface $container) : TraktApi ); } + // phpcs:ignore Generic.Metrics.CyclomaticComplexity public static function createTwigEnvironment(ContainerInterface $container) : Twig\Environment { $twig = new Twig\Environment($container->get(Twig\Loader\LoaderInterface::class)); @@ -304,6 +305,7 @@ public static function createTwigEnvironment(ContainerInterface $container) : Tw $twig->addGlobal('currentUserName', $user?->getName()); $twig->addGlobal('currentUserIsAdmin', $user?->isAdmin()); $twig->addGlobal('currentUserCountry', $user?->getCountry()); + $twig->addGlobal('currentUserLocationsEnabled', $user?->hasLocationsEnabled()); $twig->addGlobal('routeUsername', $routeUsername ?? null); $twig->addGlobal('dateFormatPhp', $dateFormatPhp); $twig->addGlobal('dateFormatJavascript', $dataFormatJavascript); diff --git a/src/HttpController/Web/SettingsController.php b/src/HttpController/Web/SettingsController.php index 4eb361c7..ada638d0 100644 --- a/src/HttpController/Web/SettingsController.php +++ b/src/HttpController/Web/SettingsController.php @@ -178,9 +178,13 @@ public function renderDashboardAccountPage() : Response public function renderLocationsAccountPage() : Response { + $user = $this->authenticationService->getCurrentUser(); + return Response::create( StatusCode::createOk(), - $this->twig->render('page/settings-account-locations.html.twig'), + $this->twig->render('page/settings-account-locations.html.twig', [ + 'locationsEnabled' => $user->hasLocationsEnabled(), + ]), ); } diff --git a/templates/page/settings-account-locations.html.twig b/templates/page/settings-account-locations.html.twig index f6d92eb6..808457e8 100644 --- a/templates/page/settings-account-locations.html.twig +++ b/templates/page/settings-account-locations.html.twig @@ -23,7 +23,13 @@
Locations
- +

You can set the location where you have watched a movie.

+ + {% if locationsEnabled %} + + {% else %} + + {% endif %}
@@ -38,11 +44,11 @@
NameManage your locations
-
+
Loading...
- +
From 2402e745673e00faa7d1ddaf79a151cb14e35e9c Mon Sep 17 00:00:00 2001 From: Lee Peuker Date: Sun, 1 Sep 2024 19:39:45 +0200 Subject: [PATCH 4/4] Update UI to hide location elements if feature is disabled --- templates/component/modal-edit-watch-date.html.twig | 2 +- templates/component/modal-log-play.html.twig | 2 +- templates/page/movie.html.twig | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/component/modal-edit-watch-date.html.twig b/templates/component/modal-edit-watch-date.html.twig index 730ae505..92e8c5fb 100644 --- a/templates/component/modal-edit-watch-date.html.twig +++ b/templates/component/modal-edit-watch-date.html.twig @@ -25,7 +25,7 @@ -
+
diff --git a/templates/component/modal-log-play.html.twig b/templates/component/modal-log-play.html.twig index 68721f7e..fb451700 100644 --- a/templates/component/modal-log-play.html.twig +++ b/templates/component/modal-log-play.html.twig @@ -80,7 +80,7 @@
-
+
diff --git a/templates/page/movie.html.twig b/templates/page/movie.html.twig index ef01d940..1cf25a56 100644 --- a/templates/page/movie.html.twig +++ b/templates/page/movie.html.twig @@ -143,7 +143,7 @@ {% if watchDate.comment != '' %} {% endif %} - {% if watchDate.location_id != '' %} + {% if watchDate.location_id != '' and currentUserLocationsEnabled %} {% endif %}