From 7490bbfc9e2698b32b95dc87a844a15981ac0e88 Mon Sep 17 00:00:00 2001 From: Ryan Cohen Date: Tue, 16 Aug 2022 15:55:32 +0900 Subject: [PATCH] feat: add permissions for watchlist sync --- server/entity/UserSettings.ts | 5 +- .../interfaces/api/userSettingsInterfaces.ts | 3 +- server/lib/permissions.ts | 3 + server/lib/watchlistsync.ts | 40 ++++++++- ...0632269368-AddWatchlistSyncUserSetting.ts} | 6 +- server/routes/user/usersettings.ts | 12 ++- src/components/PermissionEdit/index.tsx | 46 ++++++++++ .../UserGeneralSettings/index.tsx | 88 ++++++++++++++----- src/i18n/locale/en.json | 10 +++ 9 files changed, 182 insertions(+), 31 deletions(-) rename server/migration/{1660627491586-AddWatchlistSyncUserSetting.ts => 1660632269368-AddWatchlistSyncUserSetting.ts} (87%) diff --git a/server/entity/UserSettings.ts b/server/entity/UserSettings.ts index a3e6c7aa5..6def14f41 100644 --- a/server/entity/UserSettings.ts +++ b/server/entity/UserSettings.ts @@ -58,7 +58,10 @@ export class UserSettings { public telegramSendSilently?: boolean; @Column({ nullable: true }) - public watchlistSync?: boolean; + public watchlistSyncMovies?: boolean; + + @Column({ nullable: true }) + public watchlistSyncTv?: boolean; @Column({ type: 'text', diff --git a/server/interfaces/api/userSettingsInterfaces.ts b/server/interfaces/api/userSettingsInterfaces.ts index 9f8aa0bab..eb7fe0f99 100644 --- a/server/interfaces/api/userSettingsInterfaces.ts +++ b/server/interfaces/api/userSettingsInterfaces.ts @@ -14,7 +14,8 @@ export interface UserSettingsGeneralResponse { globalMovieQuotaLimit?: number; globalTvQuotaLimit?: number; globalTvQuotaDays?: number; - watchlistSync?: boolean; + watchlistSyncMovies?: boolean; + watchlistSyncTv?: boolean; } export type NotificationAgentTypes = Record; diff --git a/server/lib/permissions.ts b/server/lib/permissions.ts index ce14f7a1a..5c85c6c91 100644 --- a/server/lib/permissions.ts +++ b/server/lib/permissions.ts @@ -21,6 +21,9 @@ export enum Permission { MANAGE_ISSUES = 1048576, VIEW_ISSUES = 2097152, CREATE_ISSUES = 4194304, + AUTO_REQUEST = 8388608, + AUTO_REQUEST_MOVIE = 16777216, + AUTO_REQUEST_TV = 33554432, RECENT_VIEW = 67108864, } diff --git a/server/lib/watchlistsync.ts b/server/lib/watchlistsync.ts index 3f74f4272..aa3332b6b 100644 --- a/server/lib/watchlistsync.ts +++ b/server/lib/watchlistsync.ts @@ -12,6 +12,7 @@ import { QuotaRestrictedError, RequestPermissionError, } from '../entity/MediaRequest'; +import { Permission } from './permissions'; class WatchlistSync { public async syncWatchlist() { @@ -37,8 +38,24 @@ class WatchlistSync { return; } - // Skip sync if user settings have it disabled - if (!user.settings?.watchlistSync) { + if ( + !user.hasPermission( + [ + Permission.AUTO_REQUEST, + Permission.AUTO_REQUEST_MOVIE, + Permission.AUTO_APPROVE_TV, + ], + { type: 'or' } + ) + ) { + return; + } + + if ( + !user.settings?.watchlistSyncMovies && + !user.settings?.watchlistSyncTv + ) { + // Skip sync if user settings have it disabled return; } @@ -74,6 +91,25 @@ class WatchlistSync { throw new Error('Missing TVDB ID from Plex Metadata'); } + // Check if they have auto-request permissons and watchlist sync + // enabled for the media type + if ( + ((!user.hasPermission( + [Permission.AUTO_REQUEST, Permission.AUTO_REQUEST_MOVIE], + { type: 'or' } + ) || + !user.settings?.watchlistSyncMovies) && + mediaItem.type === 'movie') || + ((!user.hasPermission( + [Permission.AUTO_REQUEST, Permission.AUTO_REQUEST_TV], + { type: 'or' } + ) || + !user.settings?.watchlistSyncTv) && + mediaItem.type === 'show') + ) { + return; + } + await MediaRequest.request( { mediaId: mediaItem.tmdbId, diff --git a/server/migration/1660627491586-AddWatchlistSyncUserSetting.ts b/server/migration/1660632269368-AddWatchlistSyncUserSetting.ts similarity index 87% rename from server/migration/1660627491586-AddWatchlistSyncUserSetting.ts rename to server/migration/1660632269368-AddWatchlistSyncUserSetting.ts index 2067d1895..c0d0e947f 100644 --- a/server/migration/1660627491586-AddWatchlistSyncUserSetting.ts +++ b/server/migration/1660632269368-AddWatchlistSyncUserSetting.ts @@ -1,13 +1,13 @@ import type { MigrationInterface, QueryRunner } from 'typeorm'; -export class AddWatchlistSyncUserSetting1660627491586 +export class AddWatchlistSyncUserSetting1660632269368 implements MigrationInterface { - name = 'AddWatchlistSyncUserSetting1660627491586'; + name = 'AddWatchlistSyncUserSetting1660632269368'; public async up(queryRunner: QueryRunner): Promise { await queryRunner.query( - `CREATE TABLE "temporary_user_settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "notificationTypes" text, "discordId" varchar, "userId" integer, "region" varchar, "originalLanguage" varchar, "telegramChatId" varchar, "telegramSendSilently" boolean, "pgpKey" varchar, "locale" varchar NOT NULL DEFAULT (''), "pushbulletAccessToken" varchar, "pushoverApplicationToken" varchar, "pushoverUserKey" varchar, "watchlistSync" boolean, CONSTRAINT "UQ_986a2b6d3c05eb4091bb8066f78" UNIQUE ("userId"), CONSTRAINT "FK_986a2b6d3c05eb4091bb8066f78" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)` + `CREATE TABLE "temporary_user_settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "notificationTypes" text, "discordId" varchar, "userId" integer, "region" varchar, "originalLanguage" varchar, "telegramChatId" varchar, "telegramSendSilently" boolean, "pgpKey" varchar, "locale" varchar NOT NULL DEFAULT (''), "pushbulletAccessToken" varchar, "pushoverApplicationToken" varchar, "pushoverUserKey" varchar, "watchlistSyncMovies" boolean, "watchlistSyncTv" boolean, CONSTRAINT "UQ_986a2b6d3c05eb4091bb8066f78" UNIQUE ("userId"), CONSTRAINT "FK_986a2b6d3c05eb4091bb8066f78" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)` ); await queryRunner.query( `INSERT INTO "temporary_user_settings"("id", "notificationTypes", "discordId", "userId", "region", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey") SELECT "id", "notificationTypes", "discordId", "userId", "region", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey" FROM "user_settings"` diff --git a/server/routes/user/usersettings.ts b/server/routes/user/usersettings.ts index 2e5af2f7e..8b80175d1 100644 --- a/server/routes/user/usersettings.ts +++ b/server/routes/user/usersettings.ts @@ -63,7 +63,8 @@ userSettingsRoutes.get<{ id: string }, UserSettingsGeneralResponse>( globalMovieQuotaLimit: defaultQuotas.movie.quotaLimit, globalTvQuotaDays: defaultQuotas.tv.quotaDays, globalTvQuotaLimit: defaultQuotas.tv.quotaLimit, - watchlistSync: user.settings?.watchlistSync, + watchlistSyncMovies: user.settings?.watchlistSyncMovies, + watchlistSyncTv: user.settings?.watchlistSyncTv, }); } catch (e) { next({ status: 500, message: e.message }); @@ -115,14 +116,16 @@ userSettingsRoutes.post< locale: req.body.locale, region: req.body.region, originalLanguage: req.body.originalLanguage, - watchlistSync: req.body.watchlistSync, + watchlistSyncMovies: req.body.watchlistSyncMovies, + watchlistSyncTv: req.body.watchlistSyncTv, }); } else { user.settings.discordId = req.body.discordId; user.settings.locale = req.body.locale; user.settings.region = req.body.region; user.settings.originalLanguage = req.body.originalLanguage; - user.settings.watchlistSync = req.body.watchlistSync; + user.settings.watchlistSyncMovies = req.body.watchlistSyncMovies; + user.settings.watchlistSyncTv = req.body.watchlistSyncTv; } await userRepository.save(user); @@ -133,7 +136,8 @@ userSettingsRoutes.post< locale: user.settings.locale, region: user.settings.region, originalLanguage: user.settings.originalLanguage, - watchlistSync: user.settings.watchlistSync, + watchlistSyncMovies: user.settings.watchlistSyncMovies, + watchlistSyncTv: user.settings.watchlistSyncTv, }); } catch (e) { next({ status: 500, message: e.message }); diff --git a/src/components/PermissionEdit/index.tsx b/src/components/PermissionEdit/index.tsx index 1a4eacd83..3f70771a7 100644 --- a/src/components/PermissionEdit/index.tsx +++ b/src/components/PermissionEdit/index.tsx @@ -50,6 +50,15 @@ export const messages = defineMessages({ advancedrequest: 'Advanced Requests', advancedrequestDescription: 'Grant permission to modify advanced media request options.', + autorequest: 'Auto-Request', + autorequestDescription: + 'Grant permission to use Plex Watchlist Sync to automatically request media.', + autorequestMovies: 'Auto-Request Movies', + autorequestMoviesDescription: + 'Grant permission to use Plex Watchlist Sync to automatically request movies.', + autorequestSeries: 'Auto-Request Series', + autorequestSeriesDescription: + 'Grant permission to use Plex Watchlist Sync to automatically request series.', viewrequests: 'View Requests', viewrequestsDescription: 'Grant permission to view media requests submitted by other users.', @@ -176,6 +185,43 @@ export const PermissionEdit = ({ }, ], }, + { + id: 'autorequest', + name: intl.formatMessage(messages.autorequest), + description: intl.formatMessage(messages.autorequestDescription), + permission: Permission.AUTO_REQUEST, + requires: [{ permissions: [Permission.REQUEST] }], + children: [ + { + id: 'autorequestmovies', + name: intl.formatMessage(messages.autorequestMovies), + description: intl.formatMessage( + messages.autorequestMoviesDescription + ), + permission: Permission.AUTO_REQUEST_MOVIE, + requires: [ + { + permissions: [Permission.REQUEST, Permission.REQUEST_MOVIE], + type: 'or', + }, + ], + }, + { + id: 'autorequesttv', + name: intl.formatMessage(messages.autorequestSeries), + description: intl.formatMessage( + messages.autorequestSeriesDescription + ), + permission: Permission.AUTO_REQUEST_TV, + requires: [ + { + permissions: [Permission.REQUEST, Permission.REQUEST_TV], + type: 'or', + }, + ], + }, + ], + }, { id: 'request4k', name: intl.formatMessage(messages.request4k), diff --git a/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx b/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx index 1bcc998b5..cccff3541 100644 --- a/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx +++ b/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx @@ -49,8 +49,12 @@ const messages = defineMessages({ discordIdTip: 'The multi-digit ID number associated with your Discord user account', validationDiscordId: 'You must provide a valid Discord user ID', - plexwatchlistsync: 'Plex Watchlist Auto-Request', - plexwatchlistsynctip: 'Automatically request Plex watchlist items.', + plexwatchlistsyncmovies: 'Auto-Request Movies', + plexwatchlistsyncmoviestip: + 'Automatically request movies on your Plex watchlist', + plexwatchlistsyncseries: 'Auto-Request Series', + plexwatchlistsyncseriestip: + 'Automatically request series on your Plex watchlist', }); const UserGeneralSettings = () => { @@ -124,7 +128,8 @@ const UserGeneralSettings = () => { movieQuotaDays: data?.movieQuotaDays, tvQuotaLimit: data?.tvQuotaLimit, tvQuotaDays: data?.tvQuotaDays, - watchlistSync: false, + watchlistSyncMovies: data?.watchlistSyncMovies, + watchlistSyncTv: data?.watchlistSyncTv, }} validationSchema={UserGeneralSettingsSchema} enableReinitialize @@ -142,6 +147,8 @@ const UserGeneralSettings = () => { movieQuotaDays: movieQuotaEnabled ? values.movieQuotaDays : null, tvQuotaLimit: tvQuotaEnabled ? values.tvQuotaLimit : null, tvQuotaDays: tvQuotaEnabled ? values.tvQuotaDays : null, + watchlistSyncMovies: values.watchlistSyncMovies, + watchlistSyncTv: values.watchlistSyncTv, }); if (currentUser?.id === user?.id && setLocale) { @@ -412,24 +419,65 @@ const UserGeneralSettings = () => { )} -
- -
- { - setFieldValue('watchlistSync', !values.watchlistSync); - }} - /> + {hasPermission( + [Permission.AUTO_REQUEST, Permission.AUTO_REQUEST_MOVIE], + { type: 'or' } + ) && ( +
+ +
+ { + setFieldValue( + 'watchlistSyncMovies', + !values.watchlistSyncMovies + ); + }} + /> +
-
+ )} + {hasPermission( + [Permission.AUTO_REQUEST, Permission.AUTO_REQUEST_TV], + { type: 'or' } + ) && ( +
+ +
+ { + setFieldValue( + 'watchlistSyncTv', + !values.watchlistSyncTv + ); + }} + /> +
+
+ )}
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index c74ea0e15..973e60329 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -232,6 +232,12 @@ "components.PermissionEdit.autoapproveMoviesDescription": "Grant automatic approval for non-4K movie requests.", "components.PermissionEdit.autoapproveSeries": "Auto-Approve Series", "components.PermissionEdit.autoapproveSeriesDescription": "Grant automatic approval for non-4K series requests.", + "components.PermissionEdit.autorequest": "Auto-Request", + "components.PermissionEdit.autorequestDescription": "Grant permission to use Plex Watchlist Sync to automatically request media.", + "components.PermissionEdit.autorequestMovies": "Auto-Request Movies", + "components.PermissionEdit.autorequestMoviesDescription": "Grant permission to use Plex Watchlist Sync to automatically request movies.", + "components.PermissionEdit.autorequestSeries": "Auto-Request Series", + "components.PermissionEdit.autorequestSeriesDescription": "Grant permission to use Plex Watchlist Sync to automatically request series.", "components.PermissionEdit.createissues": "Report Issues", "components.PermissionEdit.createissuesDescription": "Grant permission to report media issues.", "components.PermissionEdit.manageissues": "Manage Issues", @@ -923,6 +929,10 @@ "components.UserProfile.UserSettings.UserGeneralSettings.originallanguageTip": "Filter content by original language", "components.UserProfile.UserSettings.UserGeneralSettings.owner": "Owner", "components.UserProfile.UserSettings.UserGeneralSettings.plexuser": "Plex User", + "components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmovies": "Auto-Request Movies", + "components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmoviestip": "Automatically request movies on your Plex watchlist", + "components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseries": "Auto-Request Series", + "components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseriestip": "Automatically request series on your Plex watchlist", "components.UserProfile.UserSettings.UserGeneralSettings.region": "Discover Region", "components.UserProfile.UserSettings.UserGeneralSettings.regionTip": "Filter content by regional availability", "components.UserProfile.UserSettings.UserGeneralSettings.role": "Role",