mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2026-01-11 09:06:55 -05:00
Compare commits
5 Commits
pr-2273
...
fallenbage
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdfe333980 | ||
|
|
adbcf80333 | ||
|
|
f91a26befe | ||
|
|
0c95b5ec91 | ||
|
|
193d4dc668 |
20
README.md
20
README.md
@@ -32,10 +32,28 @@ With more features on the way! Check out our [issue tracker](/../../issues) to s
|
||||
|
||||
## Getting Started
|
||||
|
||||
Check out our documentation for instructions on how to install and run Seerr:
|
||||
For instructions on how to install and run **Jellyseerr**, please refer to the official documentation:
|
||||
|
||||
https://docs.seerr.dev/getting-started/
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Seerr is not officially released yet.**
|
||||
> The project is currently available **only on the `develop` branch** and is intended for **beta testing only**.
|
||||
|
||||
The documentation linked above is for running the **latest Jellyseerr** release.
|
||||
|
||||
> [!WARNING]
|
||||
> 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):
|
||||
https://github.com/seerr-team/seerr/blob/develop/docs/migration-guide.mdx
|
||||
|
||||
> [!CAUTION]
|
||||
> **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.**
|
||||
> Doing so **may cause database corruption and/or irreversible data loss and/or weird unintended behaviour**.
|
||||
|
||||
For migration assistance, beta testing questions, or troubleshooting, please join our **Discord** and ask for support there.
|
||||
|
||||
## Preview
|
||||
|
||||
<img src="./public/preview.jpg">
|
||||
|
||||
@@ -13,9 +13,7 @@ const migrationArrTags = async (settings: any): Promise<AllSettings> => {
|
||||
}
|
||||
|
||||
const userRepository = getRepository(User);
|
||||
const users = await userRepository.find({
|
||||
select: ['id'],
|
||||
});
|
||||
const users = await userRepository.find();
|
||||
|
||||
let errorOccurred = false;
|
||||
|
||||
@@ -30,15 +28,26 @@ const migrationArrTags = async (settings: any): Promise<AllSettings> => {
|
||||
});
|
||||
const radarrTags = await radarr.getTags();
|
||||
for (const user of users) {
|
||||
const userTag = radarrTags.find((v) =>
|
||||
v.label.startsWith(user.id + ' - ')
|
||||
const userTag = radarrTags.find(
|
||||
(v) =>
|
||||
v.label.startsWith(user.id + ' - ') ||
|
||||
v.label.startsWith(user.id + '-')
|
||||
);
|
||||
if (!userTag) {
|
||||
continue;
|
||||
}
|
||||
await radarr.renameTag({
|
||||
id: userTag.id,
|
||||
label: userTag.label.replace(`${user.id} - `, `${user.id}-`),
|
||||
label:
|
||||
user.id +
|
||||
'-' +
|
||||
user.displayName
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/[^a-z0-9-]/gi, '')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-|-$/g, ''),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -61,15 +70,26 @@ const migrationArrTags = async (settings: any): Promise<AllSettings> => {
|
||||
});
|
||||
const sonarrTags = await sonarr.getTags();
|
||||
for (const user of users) {
|
||||
const userTag = sonarrTags.find((v) =>
|
||||
v.label.startsWith(user.id + ' - ')
|
||||
const userTag = sonarrTags.find(
|
||||
(v) =>
|
||||
v.label.startsWith(user.id + ' - ') ||
|
||||
v.label.startsWith(user.id + '-')
|
||||
);
|
||||
if (!userTag) {
|
||||
continue;
|
||||
}
|
||||
await sonarr.renameTag({
|
||||
id: userTag.id,
|
||||
label: userTag.label.replace(`${user.id} - `, `${user.id}-`),
|
||||
label:
|
||||
user.id +
|
||||
'-' +
|
||||
user.displayName
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/[^a-z0-9-]/gi, '')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-|-$/g, ''),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -6,6 +6,15 @@ export class AddUniqueConstraintToPushSubscription1765233385034
|
||||
name = 'AddUniqueConstraintToPushSubscription1765233385034';
|
||||
|
||||
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(
|
||||
`ALTER TABLE "user_push_subscription" ADD CONSTRAINT "UQ_6427d07d9a171a3a1ab87480005" UNIQUE ("endpoint", "userId")`
|
||||
);
|
||||
|
||||
@@ -6,6 +6,15 @@ export class AddUniqueConstraintToPushSubscription1765233385034
|
||||
name = 'AddUniqueConstraintToPushSubscription1765233385034';
|
||||
|
||||
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(
|
||||
`CREATE UNIQUE INDEX "UQ_6427d07d9a171a3a1ab87480005" ON "user_push_subscription" ("endpoint", "userId")`
|
||||
);
|
||||
|
||||
@@ -29,6 +29,16 @@ import type {
|
||||
} 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()
|
||||
export class MediaRequestSubscriber
|
||||
implements EntitySubscriberInterface<MediaRequest>
|
||||
@@ -310,11 +320,15 @@ export class MediaRequestSubscriber
|
||||
mediaId: entity.media.id,
|
||||
userId: entity.requestedBy.id,
|
||||
newTag:
|
||||
entity.requestedBy.id + '-' + entity.requestedBy.displayName,
|
||||
entity.requestedBy.id +
|
||||
'-' +
|
||||
sanitizeDisplayName(entity.requestedBy.displayName),
|
||||
});
|
||||
userTag = await radarr.createTag({
|
||||
label:
|
||||
entity.requestedBy.id + '-' + entity.requestedBy.displayName,
|
||||
entity.requestedBy.id +
|
||||
'-' +
|
||||
sanitizeDisplayName(entity.requestedBy.displayName),
|
||||
});
|
||||
}
|
||||
if (userTag.id) {
|
||||
@@ -631,11 +645,15 @@ export class MediaRequestSubscriber
|
||||
mediaId: entity.media.id,
|
||||
userId: entity.requestedBy.id,
|
||||
newTag:
|
||||
entity.requestedBy.id + '-' + entity.requestedBy.displayName,
|
||||
entity.requestedBy.id +
|
||||
'-' +
|
||||
sanitizeDisplayName(entity.requestedBy.displayName),
|
||||
});
|
||||
userTag = await sonarr.createTag({
|
||||
label:
|
||||
entity.requestedBy.id + '-' + entity.requestedBy.displayName,
|
||||
entity.requestedBy.id +
|
||||
'-' +
|
||||
sanitizeDisplayName(entity.requestedBy.displayName),
|
||||
});
|
||||
}
|
||||
if (userTag.id) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
} from '@server/constants/media';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
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 { MovieDetails } from '@server/models/Movie';
|
||||
import type { TvDetails } from '@server/models/Tv';
|
||||
@@ -33,6 +34,17 @@ import Link from 'next/link';
|
||||
import { useIntl } from 'react-intl';
|
||||
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', {
|
||||
manageModalTitle: 'Manage {mediaType}',
|
||||
manageModalIssues: 'Open Issues',
|
||||
@@ -230,26 +242,30 @@ const ManageSlideOver = ({
|
||||
</h3>
|
||||
<div className="overflow-hidden rounded-md border border-gray-700 shadow">
|
||||
<ul>
|
||||
{data.mediaInfo?.downloadStatus?.map((status, index) => (
|
||||
<Tooltip
|
||||
key={`dl-status-${status.externalId}-${index}`}
|
||||
content={status.title}
|
||||
>
|
||||
<li className="border-b border-gray-700 last:border-b-0">
|
||||
<DownloadBlock downloadItem={status} />
|
||||
</li>
|
||||
</Tooltip>
|
||||
))}
|
||||
{data.mediaInfo?.downloadStatus4k?.map((status, index) => (
|
||||
<Tooltip
|
||||
key={`dl-status-${status.externalId}-${index}`}
|
||||
content={status.title}
|
||||
>
|
||||
<li className="border-b border-gray-700 last:border-b-0">
|
||||
<DownloadBlock downloadItem={status} is4k />
|
||||
</li>
|
||||
</Tooltip>
|
||||
))}
|
||||
{filterDuplicateDownloads(data.mediaInfo?.downloadStatus).map(
|
||||
(status, index) => (
|
||||
<Tooltip
|
||||
key={`dl-status-${status.externalId}-${index}`}
|
||||
content={status.title}
|
||||
>
|
||||
<li className="border-b border-gray-700 last:border-b-0">
|
||||
<DownloadBlock downloadItem={status} />
|
||||
</li>
|
||||
</Tooltip>
|
||||
)
|
||||
)}
|
||||
{filterDuplicateDownloads(data.mediaInfo?.downloadStatus4k).map(
|
||||
(status, index) => (
|
||||
<Tooltip
|
||||
key={`dl-status-4k-${status.externalId}-${index}`}
|
||||
content={status.title}
|
||||
>
|
||||
<li className="border-b border-gray-700 last:border-b-0">
|
||||
<DownloadBlock downloadItem={status} is4k />
|
||||
</li>
|
||||
</Tooltip>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,8 @@ const LoginWithPlex = ({ onComplete }: LoginWithPlexProps) => {
|
||||
const response = await axios.post('/api/v1/auth/plex', { authToken });
|
||||
|
||||
if (response.data?.id) {
|
||||
revalidate();
|
||||
const { data: user } = await axios.get('/api/v1/auth/me');
|
||||
revalidate(user, false);
|
||||
}
|
||||
};
|
||||
if (authToken) {
|
||||
|
||||
Reference in New Issue
Block a user