Compare commits
1 Commits
preview-pl
...
pr-2273
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb672ec3c4 |
@@ -45,12 +45,12 @@ The documentation linked above is for running the **latest Jellyseerr** release.
|
|||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> If you are migrating from **Overseerr** to **Seerr** for beta testing, **do not follow the Jellyseerr latest setup guide**.
|
> If you are migrating from **Overseerr** to **Seerr** for beta testing, **do not follow the Jellyseerr latest setup guide**.
|
||||||
|
|
||||||
Instead, follow the dedicated migration guide (with `:develop` tag):
|
Instead, follow the dedicated migration guide:
|
||||||
https://github.com/seerr-team/seerr/blob/develop/docs/migration-guide.mdx
|
https://github.com/seerr-team/seerr/blob/develop/docs/migration-guide.mdx
|
||||||
|
|
||||||
> [!CAUTION]
|
> [!DANGER]
|
||||||
> **DO NOT run Jellyseerr (latest) using an existing Overseerr database. This includes third-party images with `seerr:latest` (as it points to jellyseerr 2.7.3 and not seerr.**
|
> **DO NOT run Jellyseerr (latest) using an existing Overseerr database.**
|
||||||
> Doing so **may cause database corruption and/or irreversible data loss and/or weird unintended behaviour**.
|
> Doing so **will cause database corruption and/or irreversible data loss**.
|
||||||
|
|
||||||
For migration assistance, beta testing questions, or troubleshooting, please join our **Discord** and ask for support there.
|
For migration assistance, beta testing questions, or troubleshooting, please join our **Discord** and ask for support there.
|
||||||
|
|
||||||
|
|||||||
@@ -666,16 +666,6 @@ class AvailabilitySync {
|
|||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
let existsInRadarr = false;
|
let existsInRadarr = false;
|
||||||
|
|
||||||
if (is4k && media.status4k === MediaStatus.AVAILABLE) {
|
|
||||||
logger.debug(
|
|
||||||
`Checking if 4K movie [TMDB ID ${media.tmdbId}] exists in Radarr`,
|
|
||||||
{
|
|
||||||
label: 'AvailabilitySync',
|
|
||||||
externalServiceId4k: media.externalServiceId4k,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for availability in all of the available radarr servers
|
// Check for availability in all of the available radarr servers
|
||||||
// If any find the media, we will assume the media exists
|
// If any find the media, we will assume the media exists
|
||||||
for (const server of this.radarrServers.filter(
|
for (const server of this.radarrServers.filter(
|
||||||
@@ -880,32 +870,6 @@ class AvailabilitySync {
|
|||||||
this.plexSeasonsCache[ratingKey4k] =
|
this.plexSeasonsCache[ratingKey4k] =
|
||||||
await this.plexClient?.getChildrenMetadata(ratingKey4k);
|
await this.plexClient?.getChildrenMetadata(ratingKey4k);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plexMedia) {
|
|
||||||
if (media.mediaType === 'movie') {
|
|
||||||
const has4kByWidth = plexMedia.Media?.some(
|
|
||||||
(mediaItem) => (mediaItem.width ?? 0) >= 2000
|
|
||||||
);
|
|
||||||
|
|
||||||
if (is4k) {
|
|
||||||
if (ratingKey === ratingKey4k || !has4kByWidth) {
|
|
||||||
plexMedia = undefined;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const hasNon4kByWidth = plexMedia.Media?.some(
|
|
||||||
(mediaItem) =>
|
|
||||||
(mediaItem.width ?? 0) < 2000 && (mediaItem.width ?? 0) > 0
|
|
||||||
);
|
|
||||||
if (!hasNon4kByWidth && has4kByWidth) {
|
|
||||||
plexMedia = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (media.mediaType === 'tv' && is4k) {
|
|
||||||
if (ratingKey === ratingKey4k) {
|
|
||||||
plexMedia = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plexMedia) {
|
if (plexMedia) {
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ const migrationArrTags = async (settings: any): Promise<AllSettings> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const userRepository = getRepository(User);
|
const userRepository = getRepository(User);
|
||||||
const users = await userRepository.find();
|
const users = await userRepository.find({
|
||||||
|
select: ['id'],
|
||||||
|
});
|
||||||
|
|
||||||
let errorOccurred = false;
|
let errorOccurred = false;
|
||||||
|
|
||||||
@@ -28,26 +30,15 @@ const migrationArrTags = async (settings: any): Promise<AllSettings> => {
|
|||||||
});
|
});
|
||||||
const radarrTags = await radarr.getTags();
|
const radarrTags = await radarr.getTags();
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
const userTag = radarrTags.find(
|
const userTag = radarrTags.find((v) =>
|
||||||
(v) =>
|
v.label.startsWith(user.id + ' - ')
|
||||||
v.label.startsWith(user.id + ' - ') ||
|
|
||||||
v.label.startsWith(user.id + '-')
|
|
||||||
);
|
);
|
||||||
if (!userTag) {
|
if (!userTag) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
await radarr.renameTag({
|
await radarr.renameTag({
|
||||||
id: userTag.id,
|
id: userTag.id,
|
||||||
label:
|
label: userTag.label.replace(`${user.id} - `, `${user.id}-`),
|
||||||
user.id +
|
|
||||||
'-' +
|
|
||||||
user.displayName
|
|
||||||
.normalize('NFD')
|
|
||||||
.replace(/[\u0300-\u036f]/g, '')
|
|
||||||
.replace(/\s+/g, '-')
|
|
||||||
.replace(/[^a-z0-9-]/gi, '')
|
|
||||||
.replace(/-+/g, '-')
|
|
||||||
.replace(/^-|-$/g, ''),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -70,26 +61,15 @@ const migrationArrTags = async (settings: any): Promise<AllSettings> => {
|
|||||||
});
|
});
|
||||||
const sonarrTags = await sonarr.getTags();
|
const sonarrTags = await sonarr.getTags();
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
const userTag = sonarrTags.find(
|
const userTag = sonarrTags.find((v) =>
|
||||||
(v) =>
|
v.label.startsWith(user.id + ' - ')
|
||||||
v.label.startsWith(user.id + ' - ') ||
|
|
||||||
v.label.startsWith(user.id + '-')
|
|
||||||
);
|
);
|
||||||
if (!userTag) {
|
if (!userTag) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
await sonarr.renameTag({
|
await sonarr.renameTag({
|
||||||
id: userTag.id,
|
id: userTag.id,
|
||||||
label:
|
label: userTag.label.replace(`${user.id} - `, `${user.id}-`),
|
||||||
user.id +
|
|
||||||
'-' +
|
|
||||||
user.displayName
|
|
||||||
.normalize('NFD')
|
|
||||||
.replace(/[\u0300-\u036f]/g, '')
|
|
||||||
.replace(/\s+/g, '-')
|
|
||||||
.replace(/[^a-z0-9-]/gi, '')
|
|
||||||
.replace(/-+/g, '-')
|
|
||||||
.replace(/^-|-$/g, ''),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -6,15 +6,6 @@ export class AddUniqueConstraintToPushSubscription1765233385034
|
|||||||
name = 'AddUniqueConstraintToPushSubscription1765233385034';
|
name = 'AddUniqueConstraintToPushSubscription1765233385034';
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`
|
|
||||||
DELETE FROM "user_push_subscription"
|
|
||||||
WHERE id NOT IN (
|
|
||||||
SELECT MAX(id)
|
|
||||||
FROM "user_push_subscription"
|
|
||||||
GROUP BY "endpoint", "userId"
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
await queryRunner.query(
|
await queryRunner.query(
|
||||||
`ALTER TABLE "user_push_subscription" ADD CONSTRAINT "UQ_6427d07d9a171a3a1ab87480005" UNIQUE ("endpoint", "userId")`
|
`ALTER TABLE "user_push_subscription" ADD CONSTRAINT "UQ_6427d07d9a171a3a1ab87480005" UNIQUE ("endpoint", "userId")`
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,15 +6,6 @@ export class AddUniqueConstraintToPushSubscription1765233385034
|
|||||||
name = 'AddUniqueConstraintToPushSubscription1765233385034';
|
name = 'AddUniqueConstraintToPushSubscription1765233385034';
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`
|
|
||||||
DELETE FROM "user_push_subscription"
|
|
||||||
WHERE id NOT IN (
|
|
||||||
SELECT MAX(id)
|
|
||||||
FROM "user_push_subscription"
|
|
||||||
GROUP BY "endpoint", "userId"
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
await queryRunner.query(
|
await queryRunner.query(
|
||||||
`CREATE UNIQUE INDEX "UQ_6427d07d9a171a3a1ab87480005" ON "user_push_subscription" ("endpoint", "userId")`
|
`CREATE UNIQUE INDEX "UQ_6427d07d9a171a3a1ab87480005" ON "user_push_subscription" ("endpoint", "userId")`
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,16 +29,6 @@ import type {
|
|||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { EventSubscriber } from 'typeorm';
|
import { EventSubscriber } from 'typeorm';
|
||||||
|
|
||||||
const sanitizeDisplayName = (displayName: string): string => {
|
|
||||||
return displayName
|
|
||||||
.normalize('NFD')
|
|
||||||
.replace(/[\u0300-\u036f]/g, '')
|
|
||||||
.replace(/\s+/g, '-')
|
|
||||||
.replace(/[^a-z0-9-]/gi, '')
|
|
||||||
.replace(/-+/g, '-')
|
|
||||||
.replace(/^-|-$/g, '');
|
|
||||||
};
|
|
||||||
|
|
||||||
@EventSubscriber()
|
@EventSubscriber()
|
||||||
export class MediaRequestSubscriber
|
export class MediaRequestSubscriber
|
||||||
implements EntitySubscriberInterface<MediaRequest>
|
implements EntitySubscriberInterface<MediaRequest>
|
||||||
@@ -320,15 +310,11 @@ export class MediaRequestSubscriber
|
|||||||
mediaId: entity.media.id,
|
mediaId: entity.media.id,
|
||||||
userId: entity.requestedBy.id,
|
userId: entity.requestedBy.id,
|
||||||
newTag:
|
newTag:
|
||||||
entity.requestedBy.id +
|
entity.requestedBy.id + '-' + entity.requestedBy.displayName,
|
||||||
'-' +
|
|
||||||
sanitizeDisplayName(entity.requestedBy.displayName),
|
|
||||||
});
|
});
|
||||||
userTag = await radarr.createTag({
|
userTag = await radarr.createTag({
|
||||||
label:
|
label:
|
||||||
entity.requestedBy.id +
|
entity.requestedBy.id + '-' + entity.requestedBy.displayName,
|
||||||
'-' +
|
|
||||||
sanitizeDisplayName(entity.requestedBy.displayName),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (userTag.id) {
|
if (userTag.id) {
|
||||||
@@ -645,15 +631,11 @@ export class MediaRequestSubscriber
|
|||||||
mediaId: entity.media.id,
|
mediaId: entity.media.id,
|
||||||
userId: entity.requestedBy.id,
|
userId: entity.requestedBy.id,
|
||||||
newTag:
|
newTag:
|
||||||
entity.requestedBy.id +
|
entity.requestedBy.id + '-' + entity.requestedBy.displayName,
|
||||||
'-' +
|
|
||||||
sanitizeDisplayName(entity.requestedBy.displayName),
|
|
||||||
});
|
});
|
||||||
userTag = await sonarr.createTag({
|
userTag = await sonarr.createTag({
|
||||||
label:
|
label:
|
||||||
entity.requestedBy.id +
|
entity.requestedBy.id + '-' + entity.requestedBy.displayName,
|
||||||
'-' +
|
|
||||||
sanitizeDisplayName(entity.requestedBy.displayName),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (userTag.id) {
|
if (userTag.id) {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import Button from '@app/components/Common/Button';
|
|||||||
import { SmallLoadingSpinner } from '@app/components/Common/LoadingSpinner';
|
import { SmallLoadingSpinner } from '@app/components/Common/LoadingSpinner';
|
||||||
import usePlexLogin from '@app/hooks/usePlexLogin';
|
import usePlexLogin from '@app/hooks/usePlexLogin';
|
||||||
import defineMessages from '@app/utils/defineMessages';
|
import defineMessages from '@app/utils/defineMessages';
|
||||||
import { Fragment } from 'react';
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
const messages = defineMessages('components.Login', {
|
const messages = defineMessages('components.Login', {
|
||||||
@@ -47,12 +46,8 @@ const PlexLoginButton = ({
|
|||||||
>
|
>
|
||||||
{(chunks) => (
|
{(chunks) => (
|
||||||
<>
|
<>
|
||||||
{chunks.map((c, index) =>
|
{chunks.map((c) =>
|
||||||
typeof c === 'string' ? (
|
typeof c === 'string' ? <span>{c}</span> : c
|
||||||
<span key={index}>{c}</span>
|
|
||||||
) : (
|
|
||||||
<Fragment key={index}>{c}</Fragment>
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import {
|
|||||||
} from '@server/constants/media';
|
} from '@server/constants/media';
|
||||||
import { MediaServerType } from '@server/constants/server';
|
import { MediaServerType } from '@server/constants/server';
|
||||||
import type { MediaWatchDataResponse } from '@server/interfaces/api/mediaInterfaces';
|
import type { MediaWatchDataResponse } from '@server/interfaces/api/mediaInterfaces';
|
||||||
import type { DownloadingItem } from '@server/lib/downloadtracker';
|
|
||||||
import type { RadarrSettings, SonarrSettings } from '@server/lib/settings';
|
import type { RadarrSettings, SonarrSettings } from '@server/lib/settings';
|
||||||
import type { MovieDetails } from '@server/models/Movie';
|
import type { MovieDetails } from '@server/models/Movie';
|
||||||
import type { TvDetails } from '@server/models/Tv';
|
import type { TvDetails } from '@server/models/Tv';
|
||||||
@@ -34,17 +33,6 @@ import Link from 'next/link';
|
|||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
const filterDuplicateDownloads = (
|
|
||||||
items: DownloadingItem[] = []
|
|
||||||
): DownloadingItem[] => {
|
|
||||||
const seen = new Set<string>();
|
|
||||||
return items.filter((item) => {
|
|
||||||
if (seen.has(item.downloadId)) return false;
|
|
||||||
seen.add(item.downloadId);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const messages = defineMessages('components.ManageSlideOver', {
|
const messages = defineMessages('components.ManageSlideOver', {
|
||||||
manageModalTitle: 'Manage {mediaType}',
|
manageModalTitle: 'Manage {mediaType}',
|
||||||
manageModalIssues: 'Open Issues',
|
manageModalIssues: 'Open Issues',
|
||||||
@@ -242,30 +230,26 @@ const ManageSlideOver = ({
|
|||||||
</h3>
|
</h3>
|
||||||
<div className="overflow-hidden rounded-md border border-gray-700 shadow">
|
<div className="overflow-hidden rounded-md border border-gray-700 shadow">
|
||||||
<ul>
|
<ul>
|
||||||
{filterDuplicateDownloads(data.mediaInfo?.downloadStatus).map(
|
{data.mediaInfo?.downloadStatus?.map((status, index) => (
|
||||||
(status, index) => (
|
<Tooltip
|
||||||
<Tooltip
|
key={`dl-status-${status.externalId}-${index}`}
|
||||||
key={`dl-status-${status.externalId}-${index}`}
|
content={status.title}
|
||||||
content={status.title}
|
>
|
||||||
>
|
<li className="border-b border-gray-700 last:border-b-0">
|
||||||
<li className="border-b border-gray-700 last:border-b-0">
|
<DownloadBlock downloadItem={status} />
|
||||||
<DownloadBlock downloadItem={status} />
|
</li>
|
||||||
</li>
|
</Tooltip>
|
||||||
</Tooltip>
|
))}
|
||||||
)
|
{data.mediaInfo?.downloadStatus4k?.map((status, index) => (
|
||||||
)}
|
<Tooltip
|
||||||
{filterDuplicateDownloads(data.mediaInfo?.downloadStatus4k).map(
|
key={`dl-status-${status.externalId}-${index}`}
|
||||||
(status, index) => (
|
content={status.title}
|
||||||
<Tooltip
|
>
|
||||||
key={`dl-status-4k-${status.externalId}-${index}`}
|
<li className="border-b border-gray-700 last:border-b-0">
|
||||||
content={status.title}
|
<DownloadBlock downloadItem={status} is4k />
|
||||||
>
|
</li>
|
||||||
<li className="border-b border-gray-700 last:border-b-0">
|
</Tooltip>
|
||||||
<DownloadBlock downloadItem={status} is4k />
|
))}
|
||||||
</li>
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -377,7 +377,6 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
|
|||||||
webAppUrl: data?.webAppUrl,
|
webAppUrl: data?.webAppUrl,
|
||||||
}}
|
}}
|
||||||
validationSchema={PlexSettingsSchema}
|
validationSchema={PlexSettingsSchema}
|
||||||
validateOnMount={true}
|
|
||||||
onSubmit={async (values) => {
|
onSubmit={async (values) => {
|
||||||
let toastId: string | null = null;
|
let toastId: string | null = null;
|
||||||
try {
|
try {
|
||||||
@@ -424,7 +423,6 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
|
|||||||
values,
|
values,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
setFieldValue,
|
setFieldValue,
|
||||||
setValues,
|
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
isValid,
|
isValid,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -447,12 +445,9 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
|
|||||||
availablePresets[Number(e.target.value)];
|
availablePresets[Number(e.target.value)];
|
||||||
|
|
||||||
if (targPreset) {
|
if (targPreset) {
|
||||||
setValues({
|
setFieldValue('hostname', targPreset.address);
|
||||||
...values,
|
setFieldValue('port', targPreset.port);
|
||||||
hostname: targPreset.address,
|
setFieldValue('useSsl', targPreset.ssl);
|
||||||
port: targPreset.port,
|
|
||||||
useSsl: targPreset.ssl,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -28,8 +28,7 @@ const LoginWithPlex = ({ onComplete }: LoginWithPlexProps) => {
|
|||||||
const response = await axios.post('/api/v1/auth/plex', { authToken });
|
const response = await axios.post('/api/v1/auth/plex', { authToken });
|
||||||
|
|
||||||
if (response.data?.id) {
|
if (response.data?.id) {
|
||||||
const { data: user } = await axios.get('/api/v1/auth/me');
|
revalidate();
|
||||||
revalidate(user, false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (authToken) {
|
if (authToken) {
|
||||||
|
|||||||
Reference in New Issue
Block a user