Skip to content

Commit

Permalink
Merge pull request #413 from leepeuker/add-plex-oauth
Browse files Browse the repository at this point in the history
Add Plex OAuth
  • Loading branch information
leepeuker committed Jul 1, 2023
2 parents 55fb364 + 130e53a commit 9d78346
Show file tree
Hide file tree
Showing 33 changed files with 1,317 additions and 148 deletions.
5 changes: 5 additions & 0 deletions .env.development.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# More info here: https://docs.movary.org/configuration/

# Environment
ENV=development
USER_ID=1000
Expand All @@ -21,6 +23,9 @@ DATABASE_MYSQL_ROOT_PASSWORD=movary
TMDB_API_KEY=
TMDB_ENABLE_IMAGE_CACHING=0

# Plex
#PLEX_IDENTIFIER=

# Logging
LOG_LEVEL=debug
LOG_ENABLE_STACKTRACE=1
Expand Down
5 changes: 5 additions & 0 deletions .env.production.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# More info here: https://docs.movary.org/configuration/

# Environment
ENV=production
TIMEZONE="Europe/Berlin"
Expand All @@ -17,6 +19,9 @@ DATABASE_MYSQL_CHARSET=utf8mb4
TMDB_API_KEY=
TMDB_ENABLE_IMAGE_CACHING=1

# Plex
#PLEX_IDENTIFIER=

# Logging
LOG_LEVEL=warning

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types=1);

use Phinx\Migration\AbstractMigration;

final class AddPlexOAuthColumnsToUserTable extends AbstractMigration
{
public function down() : void
{
$this->execute(
<<<SQL
ALTER TABLE user DROP COLUMN plex_client_id;
ALTER TABLE user DROP COLUMN plex_client_temporary_code;
ALTER TABLE user DROP COLUMN plex_access_token;
ALTER TABLE user DROP COLUMN plex_account_id;
ALTER TABLE user DROP COLUMN plex_server_url;
SQL,
);
}

public function up() : void
{
$this->execute(
<<<SQL
ALTER TABLE user ADD COLUMN plex_client_id CHAR(64) DEFAULT NULL AFTER trakt_client_id;
ALTER TABLE user ADD COLUMN plex_client_temporary_code CHAR(64) DEFAULT NULL AFTER plex_client_id;
ALTER TABLE user ADD COLUMN plex_access_token CHAR(128) DEFAULT NULL AFTER plex_client_temporary_code;
ALTER TABLE user ADD COLUMN plex_account_id CHAR(64) DEFAULT NULL AFTER plex_access_token;
ALTER TABLE user ADD COLUMN plex_server_url CHAR(128) DEFAULT NULL AFTER plex_account_id;
SQL,
);
}
}
160 changes: 160 additions & 0 deletions db/migrations/sqlite/20230627162519_AddPlexOAuthColumnsToUserTable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<?php declare(strict_types=1);

use Phinx\Migration\AbstractMigration;

final class AddPlexColumns extends AbstractMigration
{
public function down() : void
{
$this->execute(
<<<SQL
CREATE TABLE `tmp_user` (
`id` INTEGER,
`email` TEXT NOT NULL,
`name` TEXT NOT NULL,
`password` TEXT NOT NULL ,
`is_admin` TINYINT(1) DEFAULT 0,
`dashboard_visible_rows` TEXT DEFAULT NULL,
`dashboard_extended_rows` TEXT DEFAULT NULL,
`dashboard_order_rows` TEXT DEFAULT NULL,
`privacy_level` INTEGER DEFAULT 1,
`date_format_id` INTEGER DEFAULT 0,
`trakt_user_name` TEXT,
`plex_webhook_uuid` TEXT,
`jellyfin_webhook_uuid` TEXT,
`emby_webhook_uuid` TEXT,
`trakt_client_id` TEXT,
`jellyfin_scrobble_views` INTEGER DEFAULT 1,
`emby_scrobble_views` INTEGER DEFAULT 1,
`plex_scrobble_views` INTEGER DEFAULT 1,
`plex_scrobble_ratings` INTEGER DEFAULT 0,
`watchlist_automatic_removal_enabled` INTEGER DEFAULT 0,
`core_account_changes_disabled` INTEGER DEFAULT 0,
`created_at` TEXT NOT NULL,
PRIMARY KEY (`id`),
UNIQUE (`email`),
UNIQUE (`name`)
)
SQL,
);

$this->execute(
'INSERT INTO `tmp_user` (
`id`,
`email`,
`name`,
`password`,
`is_admin`,
`dashboard_visible_rows`,
`dashboard_extended_rows`,
`dashboard_order_rows`,
`privacy_level`,
`date_format_id`,
`trakt_user_name`,
`plex_webhook_uuid`,
`jellyfin_webhook_uuid`,
`emby_webhook_uuid`,
`trakt_client_id`,
`jellyfin_scrobble_views`,
`emby_scrobble_views`,
`plex_scrobble_views`,
`plex_scrobble_ratings`,
`watchlist_automatic_removal_enabled`,
`core_account_changes_disabled`,
`created_at`
) SELECT
`id`,
`email`,
`name`,
`password`,
`is_admin`,
`dashboard_visible_rows`,
`dashboard_extended_rows`,
`dashboard_order_rows`,
`privacy_level`,
`date_format_id`,
`trakt_user_name`,
`plex_webhook_uuid`,
`jellyfin_webhook_uuid`,
`emby_webhook_uuid`,
`trakt_client_id`,
`jellyfin_scrobble_views`,
`emby_scrobble_views`,
`plex_scrobble_views`,
`plex_scrobble_ratings`,
`watchlist_automatic_removal_enabled`,
`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(
<<<SQL
CREATE TABLE `tmp_user` (
`id` INTEGER,
`email` TEXT NOT NULL,
`name` TEXT NOT NULL,
`password` TEXT NOT NULL ,
`is_admin` TINYINT(1) DEFAULT 0,
`dashboard_visible_rows` TEXT DEFAULT NULL,
`dashboard_extended_rows` TEXT DEFAULT NULL,
`dashboard_order_rows` TEXT DEFAULT NULL,
`privacy_level` INTEGER DEFAULT 1,
`date_format_id` INTEGER DEFAULT 0,
`trakt_user_name` TEXT,
`plex_webhook_uuid` TEXT,
`jellyfin_webhook_uuid` TEXT,
`emby_webhook_uuid` TEXT,
`trakt_client_id` TEXT,
`plex_client_id` TEXT DEFAULT NULL,
`plex_client_temporary_code` TEXT DEFAULT NULL,
`plex_access_token` TEXT DEFAULT NULL,
`plex_account_id` TEXT DEFAULT NULL,
`plex_server_url` TEXT DEFAULT NULL,
`jellyfin_scrobble_views` INTEGER DEFAULT 1,
`emby_scrobble_views` INTEGER DEFAULT 1,
`plex_scrobble_views` INTEGER DEFAULT 1,
`plex_scrobble_ratings` INTEGER DEFAULT 0,
`watchlist_automatic_removal_enabled` INTEGER DEFAULT 0,
`core_account_changes_disabled` INTEGER DEFAULT 0,
`created_at` TEXT NOT NULL,
PRIMARY KEY (`id`),
UNIQUE (`email`),
UNIQUE (`name`)
)
SQL,
);
$this->execute(
'INSERT INTO `tmp_user` (
`id`,
`email`,
`name`,
`password`,
`is_admin`,
`dashboard_visible_rows`,
`dashboard_extended_rows`,
`dashboard_order_rows`,
`privacy_level`,
`date_format_id`,
`trakt_user_name`,
`plex_webhook_uuid`,
`jellyfin_webhook_uuid`,
`emby_webhook_uuid`,
`trakt_client_id`,
`jellyfin_scrobble_views`,
`emby_scrobble_views`,
`plex_scrobble_views`,
`plex_scrobble_ratings`,
`watchlist_automatic_removal_enabled`,
`core_account_changes_disabled`,
`created_at`
) SELECT * FROM user',
);
$this->execute('DROP TABLE `user`');
$this->execute('ALTER TABLE `tmp_user` RENAME TO `user`');
}
}
21 changes: 11 additions & 10 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@ The `Web UI` column is set to yes if an environment variable can alternatively b

### General

| NAME | DEFAULT VALUE | INFO | Web UI |
|:--------------------------------------------|:-----------------:|:------------------------------------------------------------------------|:------:|
| `TMDB_API_KEY` | - | **Required** (get key [here](https://www.themoviedb.org/settings/api)) | yes |
| `APPLICATION_URL` | - | Public base url of the application (e.g. `htttp://localhost`) | yes |
| `TMDB_ENABLE_IMAGE_CACHING` | `0` | More info [here](features/tmdb-data.md#image-cache) | |
| `ENABLE_REGISTRATION` | `0` | Enables public user registration | |
| `MIN_RUNTIME_IN_SECONDS_FOR_JOB_PROCESSING` | `15` | Minimum time between background jobs processing | |
| `TIMEZONE` | `"Europe/Berlin"` | Supported timezones [here](https://www.php.net/manual/en/timezones.php) | |
| `DEFAULT_LOGIN_EMAIL` | - | Email address to always autofill on login page | |
| `DEFAULT_LOGIN_PASSWORD` | - | Password to always autofill on login page | |
| NAME | DEFAULT VALUE | INFO | Web UI |
|:--------------------------------------------|:-----------------:|:-------------------------------------------------------------------------------|:------:|
| `TMDB_API_KEY` | - | **Required** (get key [here](https://www.themoviedb.org/settings/api)) | yes |
| `APPLICATION_URL` | - | Public base url of the application (e.g. `htttp://localhost`) | yes |
| `TMDB_ENABLE_IMAGE_CACHING` | `0` | More info [here](features/tmdb-data.md#image-cache) | |
| `PLEX_IDENTIFIER` | - | Required for Plex Authentication. Generate with e.g. `openssl rand -base64 32` | |
| `ENABLE_REGISTRATION` | `0` | Enables public user registration | |
| `MIN_RUNTIME_IN_SECONDS_FOR_JOB_PROCESSING` | `15` | Minimum time between background jobs processing | |
| `TIMEZONE` | `"Europe/Berlin"` | Supported timezones [here](https://www.php.net/manual/en/timezones.php) | |
| `DEFAULT_LOGIN_EMAIL` | - | Email address to always autofill on login page | |
| `DEFAULT_LOGIN_PASSWORD` | - | Password to always autofill on login page | |

### Database

Expand Down
1 change: 1 addition & 0 deletions public/css/bootstrap-icons-1.10.2.css
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ url("../fonts/bootstrap-icons.woff?24e3eb84d0bcaf83d77f904c78ac1f47") format("wo
.bi-x-circle-fill::before { content: "\f622"; }
.bi-chevron-expand::before { content: "\f283"; }
.bi-chevron-contract::before { content: "\f27d"; }
.bi-question-lg::before { content: "\f64e"; }
132 changes: 132 additions & 0 deletions public/js/settings-integration-plex.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,135 @@ async function updateScrobbleOptions() {
addAlert('alertWebhookOptionsDiv', 'Could not update scrobble options', 'danger')
});
}

async function authenticateWithPlex() {
const response = await fetch(
'/settings/plex/authentication-url',
{signal: AbortSignal.timeout(4000)}
).catch(function (error) {
document.getElementById('alertPlexServerUrlLoadingSpinner').classList.add('d-none')

console.log(error)
addAlert('alertPlexServerUrlDiv', 'Authentication did not work', 'danger')
});

if (!response.ok) {
if (response.status === 400) {
addAlert('alertPlexAuthenticationDiv', await response.text(), 'danger')

return
}

addAlert('alertPlexAuthenticationDiv', 'Authentication did not work', 'danger')

return
}

const data = await response.json()

location.href = data.authenticationUrl;
}

async function removePlexAuthentication() {
const response = await fetch(
'/settings/plex/logout',
{signal: AbortSignal.timeout(4000)}
).catch(function (error) {
console.log(error)

addAlert('alertPlexAuthenticationDiv', 'Could not remove authentication', 'danger')
});

if (!response.ok) {
addAlert('alertPlexAuthenticationDiv', 'Could not remove authentication', 'danger')

return
}

document.getElementById('plexServerUrlInput').disabled = true
document.getElementById('plexServerUrlInput').value = ''
document.getElementById('saveServerUrlButton').disabled = true
document.getElementById('verifyServerUrlButton').disabled = true

document.getElementById('authenticateWithPlexDiv').classList.remove('d-none')
document.getElementById('removeAuthenticationWithPlexDiv').classList.add('d-none')

addAlert('alertPlexAuthenticationDiv', 'Plex authentication was removed', 'success')
}

async function savePlexServerUrl() {
const response = await fetch('/settings/plex/server-url-save', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
'plexServerUrl': document.getElementById('plexServerUrlInput').value,
})
}).then(async function (response) {
return {'status': response.status, 'message': await response.text()};
}).then(function (data) {
if (data.status === 200) {
addAlert('alertPlexServerUrlDiv', 'Server URL was updated', 'success')

return
}

if (data.status === 400) {
addAlert('alertPlexServerUrlDiv', data.message, 'danger')

return
}

addAlert('alertPlexServerUrlDiv', 'Server URL could not be updated', 'danger')
}).catch(function (error) {
document.getElementById('alertPlexServerUrlLoadingSpinner').classList.add('d-none')

console.log(error)
addAlert('alertPlexServerUrlDiv', 'Server URL could not be updated', 'danger')
});
}

async function verifyPlexServerUrl() {
document.getElementById('alertPlexServerUrlLoadingSpinner').classList.remove('d-none')
removeAlert('alertPlexServerUrlDiv')

const response = await fetch('/settings/plex/server-url-verify', {
signal: AbortSignal.timeout(4000),
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
'plexServerUrl': document.getElementById('plexServerUrlInput').value,
})
}).then(async function (response) {
document.getElementById('alertPlexServerUrlLoadingSpinner').classList.add('d-none')

return {'status': response.status, 'message': await response.json()};
}).then(function (data) {
if (data.status === 200 && data.message === true) {
addAlert('alertPlexServerUrlDiv', 'Connection test successful', 'success')

return
}

if (data.status === 400) {
addAlert('alertPlexServerUrlDiv', data.message, 'danger')

return
}

addAlert('alertPlexServerUrlDiv', 'Connection test failed', 'danger')
}).catch(function (error) {
document.getElementById('alertPlexServerUrlLoadingSpinner').classList.add('d-none')

console.log(error)
addAlert('alertPlexServerUrlDiv', 'Connection test failed', 'danger')
});
}

document.getElementById('verifyServerUrlButton').disabled = document.getElementById('plexServerUrlInput').value === ''
document.getElementById('plexServerUrlInput').addEventListener('input', function (e) {
document.getElementById('verifyServerUrlButton').disabled = e.target.value === ''
});
Loading

0 comments on commit 9d78346

Please sign in to comment.