Full Code of seerr-team/seerr for AI

develop 25e376c74f7e cached
787 files
7.9 MB
2.1M tokens
1308 symbols
1 requests
Download .txt
Showing preview only (8,463K chars total). Download the full file or copy to clipboard to get everything.
Repository: seerr-team/seerr
Branch: develop
Commit: 25e376c74f7e
Files: 787
Total size: 7.9 MB

Directory structure:
gitextract_udf17ict/

├── .dockerignore
├── .editorconfig
├── .gitattributes
├── .github/
│   ├── CODEOWNERS
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug.yml
│   │   ├── config.yml
│   │   ├── documentation.yml
│   │   ├── enhancement.yml
│   │   └── maintenance.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── cliff.toml
│   ├── renovate/
│   │   ├── actions.json5
│   │   ├── docker.json5
│   │   ├── groups.json5
│   │   ├── helm.json5
│   │   ├── labels.json5
│   │   ├── pnpm.json5
│   │   └── semanticCommits.json5
│   ├── renovate.json5
│   └── workflows/
│       ├── ci.yml
│       ├── codeql.yml
│       ├── conflict_labeler.yml
│       ├── create-tag.yml
│       ├── cypress.yml
│       ├── detect-duplicate.yml
│       ├── docs-deploy.yml
│       ├── docs-link-check.yml
│       ├── helm.yml
│       ├── lint-helm-charts.yml
│       ├── preview.yml
│       ├── rebuild-issue-index.yml
│       ├── release.yml
│       ├── renovate-helm-custom-hooks.yml
│       ├── seerr-labeller.yml
│       ├── semantic-pr.yml
│       ├── stale.yml
│       ├── test-docs-deploy.yml
│       └── trivy-scan.yml
├── .gitignore
├── .husky/
│   ├── commit-msg
│   ├── pre-commit
│   └── prepare-commit-msg
├── .npmrc
├── .prettierignore
├── .prettierrc.js
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── Dockerfile.local
├── LICENSE
├── README.md
├── SECURITY.md
├── bin/
│   ├── check-i18n.js
│   ├── duplicate-detector/
│   │   ├── .gitignore
│   │   ├── build-index.mjs
│   │   ├── detect.mjs
│   │   ├── package.json
│   │   └── utils.mjs
│   └── prepare.js
├── charts/
│   └── seerr-chart/
│       ├── .helmignore
│       ├── Chart.yaml
│       ├── README.md
│       ├── README.md.gotmpl
│       ├── artifacthub-repo.yml
│       ├── templates/
│       │   ├── NOTES.txt
│       │   ├── _helpers.tpl
│       │   ├── ingress.yaml
│       │   ├── persistentvolumeclaim.yaml
│       │   ├── route.yaml
│       │   ├── service.yaml
│       │   ├── serviceaccount.yaml
│       │   ├── statefulset.yaml
│       │   └── tests/
│       │       └── test-connection.yaml
│       └── values.yaml
├── compose.postgres.yaml
├── compose.yaml
├── config/
│   ├── .gitkeep
│   ├── db/
│   │   └── .gitkeep
│   └── logs/
│       └── .gitkeep
├── cypress/
│   ├── config/
│   │   └── settings.cypress.json
│   ├── e2e/
│   │   ├── discover.cy.ts
│   │   ├── login.cy.ts
│   │   ├── movie-details.cy.ts
│   │   ├── providers/
│   │   │   └── tvdb.cy.ts
│   │   ├── pull-to-refresh.cy.ts
│   │   ├── settings/
│   │   │   ├── discover-customization.cy.ts
│   │   │   └── general-settings.cy.ts
│   │   ├── tv-details.cy.ts
│   │   └── user/
│   │       ├── auto-request-settings.cy.ts
│   │       ├── profile.cy.ts
│   │       └── user-list.cy.ts
│   ├── fixtures/
│   │   └── watchlist.json
│   ├── support/
│   │   ├── commands.ts
│   │   ├── e2e.ts
│   │   └── index.ts
│   └── tsconfig.json
├── cypress.config.ts
├── docs/
│   ├── README.md
│   ├── extending-seerr/
│   │   ├── _category_.json
│   │   ├── database-config.mdx
│   │   └── reverse-proxy.mdx
│   ├── getting-started/
│   │   ├── _category_.json
│   │   ├── buildfromsource.mdx
│   │   ├── docker.mdx
│   │   ├── index.mdx
│   │   ├── kubernetes.mdx
│   │   └── third-parties/
│   │       ├── aur.mdx
│   │       ├── index.mdx
│   │       ├── nixpkg.mdx
│   │       ├── synology.mdx
│   │       ├── truenas.mdx
│   │       └── unraid.mdx
│   ├── migration-guide.mdx
│   ├── troubleshooting.mdx
│   └── using-seerr/
│       ├── _category_.json
│       ├── advanced/
│       │   ├── index.mdx
│       │   └── verifying-signed-artifacts.mdx
│       ├── backups.md
│       ├── notifications/
│       │   ├── discord.md
│       │   ├── email.md
│       │   ├── gotify.md
│       │   ├── index.mdx
│       │   ├── ntfy.md
│       │   ├── pushbullet.md
│       │   ├── pushover.md
│       │   ├── slack.md
│       │   ├── telegram.md
│       │   ├── webhook.md
│       │   └── webpush.md
│       ├── plex/
│       │   ├── _category_.json
│       │   ├── index.md
│       │   └── watchlist-auto-request.md
│       ├── settings/
│       │   ├── _category_.json
│       │   ├── dns-caching.md
│       │   ├── general.md
│       │   ├── jobs&cache.md
│       │   ├── mediaserver.mdx
│       │   ├── notifications.mdx
│       │   ├── services.md
│       │   └── users.md
│       └── users/
│           ├── _category_.json
│           ├── adding-users.mdx
│           ├── deleting-users.md
│           ├── editing-users.md
│           └── owner.md
├── eslint.config.mts
├── gen-docs/
│   ├── .gitignore
│   ├── README.md
│   ├── babel.config.js
│   ├── blog/
│   │   ├── 2025-09-29-introducing-seerr-blog.md
│   │   ├── 2026-02-10/
│   │   │   └── seerr-release.md
│   │   ├── 2026-02-28-seerr-security-fix-release.md
│   │   └── authors.yml
│   ├── docusaurus.config.ts
│   ├── package.json
│   ├── sidebars.ts
│   ├── src/
│   │   ├── components/
│   │   │   └── SeerrVersion/
│   │   │       └── index.tsx
│   │   └── css/
│   │       └── custom.css
│   ├── static/
│   │   ├── .nojekyll
│   │   └── CNAME
│   ├── tailwind.config.js
│   └── tsconfig.json
├── next-env.d.ts
├── next.config.js
├── package.json
├── postcss.config.js
├── public/
│   ├── offline.html
│   ├── robots.txt
│   ├── site.webmanifest
│   └── sw.js
├── seerr-api.yml
├── server/
│   ├── api/
│   │   ├── animelist.ts
│   │   ├── externalapi.ts
│   │   ├── github.ts
│   │   ├── jellyfin.ts
│   │   ├── metadata.ts
│   │   ├── plexapi.ts
│   │   ├── plextv.ts
│   │   ├── provider.ts
│   │   ├── pushover.ts
│   │   ├── rating/
│   │   │   ├── imdbRadarrProxy.ts
│   │   │   └── rottentomatoes.ts
│   │   ├── ratings.ts
│   │   ├── servarr/
│   │   │   ├── base.ts
│   │   │   ├── radarr.ts
│   │   │   └── sonarr.ts
│   │   ├── tautulli.ts
│   │   ├── themoviedb/
│   │   │   ├── constants.ts
│   │   │   ├── index.ts
│   │   │   └── interfaces.ts
│   │   └── tvdb/
│   │       ├── index.ts
│   │       └── interfaces.ts
│   ├── constants/
│   │   ├── discover.ts
│   │   ├── error.ts
│   │   ├── issue.ts
│   │   ├── media.ts
│   │   ├── server.ts
│   │   └── user.ts
│   ├── datasource.ts
│   ├── entity/
│   │   ├── Blocklist.ts
│   │   ├── DiscoverSlider.ts
│   │   ├── Issue.ts
│   │   ├── IssueComment.ts
│   │   ├── Media.ts
│   │   ├── MediaRequest.ts
│   │   ├── OverrideRule.ts
│   │   ├── Season.ts
│   │   ├── SeasonRequest.ts
│   │   ├── Session.ts
│   │   ├── User.ts
│   │   ├── UserPushSubscription.ts
│   │   ├── UserSettings.ts
│   │   └── Watchlist.ts
│   ├── index.ts
│   ├── interfaces/
│   │   └── api/
│   │       ├── blocklistInterfaces.ts
│   │       ├── common.ts
│   │       ├── discoverInterfaces.ts
│   │       ├── issueInterfaces.ts
│   │       ├── mediaInterfaces.ts
│   │       ├── overrideRuleInterfaces.ts
│   │       ├── personInterfaces.ts
│   │       ├── plexInterfaces.ts
│   │       ├── requestInterfaces.ts
│   │       ├── serviceInterfaces.ts
│   │       ├── settingsInterfaces.ts
│   │       ├── userInterfaces.ts
│   │       ├── userSettingsInterfaces.ts
│   │       └── watchlistCreate.ts
│   ├── job/
│   │   ├── blocklistedTagsProcessor.ts
│   │   └── schedule.ts
│   ├── lib/
│   │   ├── availabilitySync.ts
│   │   ├── cache.ts
│   │   ├── downloadtracker.ts
│   │   ├── email/
│   │   │   ├── index.ts
│   │   │   └── openpgpEncrypt.ts
│   │   ├── imageproxy.ts
│   │   ├── notifications/
│   │   │   ├── agents/
│   │   │   │   ├── agent.ts
│   │   │   │   ├── discord.ts
│   │   │   │   ├── email.ts
│   │   │   │   ├── gotify.ts
│   │   │   │   ├── ntfy.ts
│   │   │   │   ├── pushbullet.ts
│   │   │   │   ├── pushover.ts
│   │   │   │   ├── slack.ts
│   │   │   │   ├── telegram.ts
│   │   │   │   ├── webhook.ts
│   │   │   │   └── webpush.ts
│   │   │   └── index.ts
│   │   ├── overseerrMerge.ts
│   │   ├── permissions.ts
│   │   ├── refreshToken.ts
│   │   ├── scanners/
│   │   │   ├── baseScanner.ts
│   │   │   ├── jellyfin/
│   │   │   │   └── index.ts
│   │   │   ├── plex/
│   │   │   │   └── index.ts
│   │   │   ├── radarr/
│   │   │   │   └── index.ts
│   │   │   └── sonarr/
│   │   │       └── index.ts
│   │   ├── search.ts
│   │   ├── settings/
│   │   │   ├── index.ts
│   │   │   ├── migrations/
│   │   │   │   ├── 0001_migrate_hostname.ts
│   │   │   │   ├── 0002_migrate_apitokens.ts
│   │   │   │   ├── 0003_emby_media_server_type.ts
│   │   │   │   ├── 0004_migrate_region_setting.ts
│   │   │   │   ├── 0005_migrate_network_settings.ts
│   │   │   │   ├── 0006_remove_lunasea.ts
│   │   │   │   ├── 0007_migrate_arr_tags.ts
│   │   │   │   └── 0008_migrate_blacklist_to_blocklist.ts
│   │   │   └── migrator.ts
│   │   └── watchlistsync.ts
│   ├── logger.ts
│   ├── middleware/
│   │   ├── auth.ts
│   │   ├── clearcookies.ts
│   │   └── deprecation.ts
│   ├── migration/
│   │   ├── postgres/
│   │   │   ├── 1734786061496-InitialMigration.ts
│   │   │   ├── 1734786596045-AddTelegramMessageThreadId.ts
│   │   │   ├── 1734805738349-AddOverrideRules.ts
│   │   │   ├── 1734809898562-FixNullFields.ts
│   │   │   ├── 1737320080282-AddBlacklistTagsColumn.ts
│   │   │   ├── 1743023615532-UpdateWebPush.ts
│   │   │   ├── 1743107707465-AddUserAvatarCacheFields.ts
│   │   │   ├── 1745492376568-UpdateWebPush.ts
│   │   │   ├── 1746811308203-FixIssueTimestamps.ts
│   │   │   ├── 1765233385034-AddUniqueConstraintToPushSubscription.ts
│   │   │   ├── 1770627987304-AddPerformanceIndexes.ts
│   │   │   ├── 1771080196816-RenameBlacklistToBlocklist.ts
│   │   │   ├── 1771259406751-AddForeignKeyIndexes.ts
│   │   │   ├── 1771337333450-RecoveryLinkExpirationDateTime.ts
│   │   │   ├── 1772000000000-FixBlocklistIdDefault.ts
│   │   │   └── 1772048000333-AddMediaTypeToUniqueConstraints.ts
│   │   └── sqlite/
│   │       ├── 1603944374840-InitialMigration.ts
│   │       ├── 1605085519544-SeasonStatus.ts
│   │       ├── 1606730060700-CascadeMigration.ts
│   │       ├── 1607928251245-DropImdbIdConstraint.ts
│   │       ├── 1608217312474-AddUserRequestDeleteCascades.ts
│   │       ├── 1608477467935-AddLastSeasonChangeMedia.ts
│   │       ├── 1608477467936-ForceDropImdbUniqueConstraint.ts
│   │       ├── 1609236552057-RemoveTmdbIdUniqueConstraint.ts
│   │       ├── 1610070934506-LocalUsers.ts
│   │       ├── 1610370640747-Add4kStatusFields.ts
│   │       ├── 1610522845513-AddMediaAddedFieldToMedia.ts
│   │       ├── 1611508672722-AddDisplayNameToUser.ts
│   │       ├── 1611757511674-SonarrRadarrSyncServiceFields.ts
│   │       ├── 1611801511397-AddRatingKeysToMedia.ts
│   │       ├── 1612482778137-AddResetPasswordGuidAndExpiryDate.ts
│   │       ├── 1612571545781-AddLanguageProfileId.ts
│   │       ├── 1613379909641-AddJellyfinUserParams.ts
│   │       ├── 1613412948344-ServerTypeEnum.ts
│   │       ├── 1613615266968-CreateUserSettings.ts
│   │       ├── 1613670041760-AddJellyfinDeviceId.ts
│   │       ├── 1613955393450-UpdateUserSettingsRegions.ts
│   │       ├── 1614334195680-AddTelegramSettingsToUserSettings.ts
│   │       ├── 1615333940450-AddPGPToUserSettings.ts
│   │       ├── 1616576677254-AddUserQuotaFields.ts
│   │       ├── 1617624225464-CreateTagsFieldonMediaRequest.ts
│   │       ├── 1617730837489-AddUserSettingsNotificationAgentsField.ts
│   │       ├── 1618912653565-CreateUserPushSubscriptions.ts
│   │       ├── 1619239659754-AddUserSettingsLocale.ts
│   │       ├── 1619339817343-AddUserSettingsNotificationTypes.ts
│   │       ├── 1634904083966-AddIssues.ts
│   │       ├── 1635079863457-AddPushbulletPushoverUserSettings.ts
│   │       ├── 1660632269368-AddWatchlistSyncUserSetting.ts
│   │       ├── 1660714479373-AddMediaRequestIsAutoRequestedField.ts
│   │       ├── 1672041273674-AddDiscoverSlider.ts
│   │       ├── 1682608634546-AddWatchlists.ts
│   │       ├── 1697393491630-AddUserPushoverSound.ts
│   │       ├── 1699901142442-AddBlacklist.ts
│   │       ├── 1727907530757-AddUserSettingsStreamingRegion.ts
│   │       ├── 1734287582736-AddTelegramMessageThreadId.ts
│   │       ├── 1734805733535-AddOverrideRules.ts
│   │       ├── 1737320080282-AddBlacklistTagsColumn.ts
│   │       ├── 1743023610704-UpdateWebPush.ts
│   │       ├── 1743107645301-AddUserAvatarCacheFields.ts
│   │       ├── 1745492372230-UpdateWebPush.ts
│   │       ├── 1765233385034-AddUniqueConstraintToPushSubscription.ts
│   │       ├── 1770627968781-AddPerformanceIndexes.ts
│   │       ├── 1771080196816-RenameBlacklistToBlocklist.ts
│   │       ├── 1771259394105-AddForeignKeyIndexes.ts
│   │       ├── 1771337037917-RecoveryLinkExpirationDateTime.ts
│   │       └── 1772047972752-AddMediaTypeToUniqueConstraints.ts
│   ├── models/
│   │   ├── Collection.ts
│   │   ├── Movie.ts
│   │   ├── Person.ts
│   │   ├── Search.ts
│   │   ├── Tv.ts
│   │   └── common.ts
│   ├── repositories/
│   │   └── watchlist.repository.ts
│   ├── routes/
│   │   ├── auth.test.ts
│   │   ├── auth.ts
│   │   ├── avatarproxy.ts
│   │   ├── blocklist.ts
│   │   ├── collection.ts
│   │   ├── discover.ts
│   │   ├── imageproxy.ts
│   │   ├── index.ts
│   │   ├── issue.ts
│   │   ├── issueComment.ts
│   │   ├── media.ts
│   │   ├── movie.ts
│   │   ├── overrideRule.ts
│   │   ├── person.ts
│   │   ├── request.ts
│   │   ├── search.ts
│   │   ├── service.ts
│   │   ├── settings/
│   │   │   ├── discover.ts
│   │   │   ├── index.ts
│   │   │   ├── metadata.ts
│   │   │   ├── notifications.ts
│   │   │   ├── radarr.ts
│   │   │   └── sonarr.ts
│   │   ├── tv.ts
│   │   ├── user/
│   │   │   ├── index.ts
│   │   │   └── usersettings.ts
│   │   └── watchlist.ts
│   ├── scripts/
│   │   └── prepareTestDb.ts
│   ├── subscriber/
│   │   ├── IssueCommentSubscriber.ts
│   │   ├── IssueSubscriber.ts
│   │   ├── MediaRequestSubscriber.ts
│   │   └── MediaSubscriber.ts
│   ├── templates/
│   │   └── email/
│   │       ├── generatedpassword/
│   │       │   ├── html.pug
│   │       │   └── subject.pug
│   │       ├── media-issue/
│   │       │   ├── html.pug
│   │       │   └── subject.pug
│   │       ├── media-request/
│   │       │   ├── html.pug
│   │       │   └── subject.pug
│   │       ├── resetpassword/
│   │       │   ├── html.pug
│   │       │   └── subject.pug
│   │       └── test-email/
│   │           ├── html.pug
│   │           └── subject.pug
│   ├── test/
│   │   ├── db.ts
│   │   ├── index.mts
│   │   └── setup.ts
│   ├── tsconfig.json
│   ├── types/
│   │   ├── custom.d.ts
│   │   ├── error.ts
│   │   ├── express-session.d.ts
│   │   ├── express.d.ts
│   │   └── languages.d.ts
│   └── utils/
│       ├── DbColumnHelper.ts
│       ├── appDataVolume.ts
│       ├── appVersion.ts
│       ├── asyncLock.ts
│       ├── customProxyAgent.ts
│       ├── dateHelpers.ts
│       ├── dnsCache.ts
│       ├── getHostname.ts
│       ├── jellyfin.ts
│       ├── profileMiddleware.ts
│       ├── restartFlag.ts
│       ├── seedTestDb.ts
│       └── typeHelpers.ts
├── src/
│   ├── components/
│   │   ├── AirDateBadge/
│   │   │   └── index.tsx
│   │   ├── AppDataWarning/
│   │   │   └── index.tsx
│   │   ├── Blocklist/
│   │   │   └── index.tsx
│   │   ├── BlocklistBlock/
│   │   │   └── index.tsx
│   │   ├── BlocklistModal/
│   │   │   └── index.tsx
│   │   ├── BlocklistedTagsBadge/
│   │   │   └── index.tsx
│   │   ├── BlocklistedTagsSelector/
│   │   │   └── index.tsx
│   │   ├── CollectionDetails/
│   │   │   └── index.tsx
│   │   ├── Common/
│   │   │   ├── Accordion/
│   │   │   │   └── index.tsx
│   │   │   ├── Alert/
│   │   │   │   └── index.tsx
│   │   │   ├── Badge/
│   │   │   │   └── index.tsx
│   │   │   ├── Button/
│   │   │   │   └── index.tsx
│   │   │   ├── ButtonWithDropdown/
│   │   │   │   └── index.tsx
│   │   │   ├── CachedImage/
│   │   │   │   └── index.tsx
│   │   │   ├── ConfirmButton/
│   │   │   │   └── index.tsx
│   │   │   ├── Dropdown/
│   │   │   │   └── index.tsx
│   │   │   ├── Header/
│   │   │   │   └── index.tsx
│   │   │   ├── ImageFader/
│   │   │   │   └── index.tsx
│   │   │   ├── LabeledCheckbox/
│   │   │   │   └── index.tsx
│   │   │   ├── List/
│   │   │   │   └── index.tsx
│   │   │   ├── ListView/
│   │   │   │   └── index.tsx
│   │   │   ├── LoadingSpinner/
│   │   │   │   └── index.tsx
│   │   │   ├── Modal/
│   │   │   │   └── index.tsx
│   │   │   ├── MultiRangeSlider/
│   │   │   │   └── index.tsx
│   │   │   ├── PageTitle/
│   │   │   │   └── index.tsx
│   │   │   ├── PlayButton/
│   │   │   │   └── index.tsx
│   │   │   ├── ProgressCircle/
│   │   │   │   └── index.tsx
│   │   │   ├── SensitiveInput/
│   │   │   │   └── index.tsx
│   │   │   ├── SettingsTabs/
│   │   │   │   └── index.tsx
│   │   │   ├── SlideCheckbox/
│   │   │   │   └── index.tsx
│   │   │   ├── SlideOver/
│   │   │   │   └── index.tsx
│   │   │   ├── StatusBadgeMini/
│   │   │   │   └── index.tsx
│   │   │   ├── Table/
│   │   │   │   └── index.tsx
│   │   │   ├── Tag/
│   │   │   │   └── index.tsx
│   │   │   └── Tooltip/
│   │   │       └── index.tsx
│   │   ├── CompanyCard/
│   │   │   └── index.tsx
│   │   ├── CompanyTag/
│   │   │   └── index.tsx
│   │   ├── Discover/
│   │   │   ├── CreateSlider/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverMovieGenre/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverMovieKeyword/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverMovieLanguage/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverMovies/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverNetwork/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverSliderEdit/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverStudio/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverTv/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverTvGenre/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverTvKeyword/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverTvLanguage/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverTvUpcoming.tsx
│   │   │   ├── DiscoverWatchlist/
│   │   │   │   └── index.tsx
│   │   │   ├── FilterSlideover/
│   │   │   │   └── index.tsx
│   │   │   ├── MovieGenreList/
│   │   │   │   └── index.tsx
│   │   │   ├── MovieGenreSlider/
│   │   │   │   └── index.tsx
│   │   │   ├── NetworkSlider/
│   │   │   │   └── index.tsx
│   │   │   ├── PlexWatchlistSlider/
│   │   │   │   └── index.tsx
│   │   │   ├── RecentRequestsSlider/
│   │   │   │   └── index.tsx
│   │   │   ├── RecentlyAddedSlider/
│   │   │   │   └── index.tsx
│   │   │   ├── StudioSlider/
│   │   │   │   └── index.tsx
│   │   │   ├── Trending.tsx
│   │   │   ├── TvGenreList/
│   │   │   │   └── index.tsx
│   │   │   ├── TvGenreSlider/
│   │   │   │   └── index.tsx
│   │   │   ├── Upcoming.tsx
│   │   │   ├── constants.ts
│   │   │   └── index.tsx
│   │   ├── DownloadBlock/
│   │   │   └── index.tsx
│   │   ├── ExternalLinkBlock/
│   │   │   └── index.tsx
│   │   ├── GenreCard/
│   │   │   └── index.tsx
│   │   ├── GenreTag/
│   │   │   └── index.tsx
│   │   ├── IssueBlock/
│   │   │   └── index.tsx
│   │   ├── IssueDetails/
│   │   │   ├── IssueComment/
│   │   │   │   └── index.tsx
│   │   │   ├── IssueDescription/
│   │   │   │   └── index.tsx
│   │   │   └── index.tsx
│   │   ├── IssueList/
│   │   │   ├── IssueItem/
│   │   │   │   └── index.tsx
│   │   │   └── index.tsx
│   │   ├── IssueModal/
│   │   │   ├── CreateIssueModal/
│   │   │   │   └── index.tsx
│   │   │   ├── constants.ts
│   │   │   └── index.tsx
│   │   ├── JSONEditor/
│   │   │   └── index.tsx
│   │   ├── KeywordTag/
│   │   │   └── index.tsx
│   │   ├── LanguageSelector/
│   │   │   └── index.tsx
│   │   ├── Layout/
│   │   │   ├── LanguagePicker/
│   │   │   │   └── index.tsx
│   │   │   ├── MobileMenu/
│   │   │   │   └── index.tsx
│   │   │   ├── Notifications/
│   │   │   │   └── index.tsx
│   │   │   ├── PullToRefresh/
│   │   │   │   └── index.tsx
│   │   │   ├── SearchInput/
│   │   │   │   └── index.tsx
│   │   │   ├── Sidebar/
│   │   │   │   └── index.tsx
│   │   │   ├── UserDropdown/
│   │   │   │   ├── MiniQuotaDisplay/
│   │   │   │   │   └── index.tsx
│   │   │   │   └── index.tsx
│   │   │   ├── UserWarnings/
│   │   │   │   └── index.tsx
│   │   │   ├── VersionStatus/
│   │   │   │   └── index.tsx
│   │   │   └── index.tsx
│   │   ├── LoadingBar/
│   │   │   └── index.tsx
│   │   ├── Login/
│   │   │   ├── AddEmailModal.tsx
│   │   │   ├── JellyfinLogin.tsx
│   │   │   ├── LocalLogin.tsx
│   │   │   ├── PlexLoginButton.tsx
│   │   │   └── index.tsx
│   │   ├── ManageSlideOver/
│   │   │   └── index.tsx
│   │   ├── MediaSlider/
│   │   │   ├── ShowMoreCard/
│   │   │   │   └── index.tsx
│   │   │   └── index.tsx
│   │   ├── MetadataSelector/
│   │   │   └── index.tsx
│   │   ├── MovieDetails/
│   │   │   ├── MovieCast/
│   │   │   │   └── index.tsx
│   │   │   ├── MovieCrew/
│   │   │   │   └── index.tsx
│   │   │   ├── MovieRecommendations.tsx
│   │   │   ├── MovieSimilar.tsx
│   │   │   └── index.tsx
│   │   ├── NotificationTypeSelector/
│   │   │   ├── NotificationType/
│   │   │   │   └── index.tsx
│   │   │   └── index.tsx
│   │   ├── PWAHeader/
│   │   │   └── index.tsx
│   │   ├── PermissionEdit/
│   │   │   └── index.tsx
│   │   ├── PermissionOption/
│   │   │   └── index.tsx
│   │   ├── PersonCard/
│   │   │   └── index.tsx
│   │   ├── PersonDetails/
│   │   │   └── index.tsx
│   │   ├── QuotaSelector/
│   │   │   └── index.tsx
│   │   ├── RegionSelector/
│   │   │   └── index.tsx
│   │   ├── RequestBlock/
│   │   │   └── index.tsx
│   │   ├── RequestButton/
│   │   │   └── index.tsx
│   │   ├── RequestCard/
│   │   │   └── index.tsx
│   │   ├── RequestList/
│   │   │   ├── RequestItem/
│   │   │   │   └── index.tsx
│   │   │   └── index.tsx
│   │   ├── RequestModal/
│   │   │   ├── AdvancedRequester/
│   │   │   │   └── index.tsx
│   │   │   ├── CollectionRequestModal.tsx
│   │   │   ├── MovieRequestModal.tsx
│   │   │   ├── QuotaDisplay/
│   │   │   │   └── index.tsx
│   │   │   ├── SearchByNameModal/
│   │   │   │   └── index.tsx
│   │   │   ├── TvRequestModal.tsx
│   │   │   └── index.tsx
│   │   ├── ResetPassword/
│   │   │   ├── RequestResetLink.tsx
│   │   │   └── index.tsx
│   │   ├── Search/
│   │   │   └── index.tsx
│   │   ├── Selector/
│   │   │   ├── CertificationSelector.tsx
│   │   │   ├── USCertificationSelector.tsx
│   │   │   └── index.tsx
│   │   ├── ServiceWorkerSetup/
│   │   │   └── index.tsx
│   │   ├── Settings/
│   │   │   ├── CopyButton.tsx
│   │   │   ├── LibraryItem.tsx
│   │   │   ├── Notifications/
│   │   │   │   ├── NotificationsDiscord.tsx
│   │   │   │   ├── NotificationsEmail.tsx
│   │   │   │   ├── NotificationsGotify/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── NotificationsNtfy/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── NotificationsPushbullet/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── NotificationsPushover/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── NotificationsSlack/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── NotificationsTelegram.tsx
│   │   │   │   ├── NotificationsWebPush/
│   │   │   │   │   └── index.tsx
│   │   │   │   └── NotificationsWebhook/
│   │   │   │       └── index.tsx
│   │   │   ├── OverrideRule/
│   │   │   │   ├── OverrideRuleModal.tsx
│   │   │   │   └── OverrideRuleTiles.tsx
│   │   │   ├── RadarrModal/
│   │   │   │   └── index.tsx
│   │   │   ├── SettingsAbout/
│   │   │   │   ├── Releases/
│   │   │   │   │   └── index.tsx
│   │   │   │   └── index.tsx
│   │   │   ├── SettingsBadge.tsx
│   │   │   ├── SettingsJellyfin.tsx
│   │   │   ├── SettingsJobsCache/
│   │   │   │   └── index.tsx
│   │   │   ├── SettingsLayout.tsx
│   │   │   ├── SettingsLogs/
│   │   │   │   └── index.tsx
│   │   │   ├── SettingsMain/
│   │   │   │   └── index.tsx
│   │   │   ├── SettingsMetadata.tsx
│   │   │   ├── SettingsNetwork/
│   │   │   │   └── index.tsx
│   │   │   ├── SettingsNotifications.tsx
│   │   │   ├── SettingsPlex.tsx
│   │   │   ├── SettingsServices.tsx
│   │   │   ├── SettingsUsers/
│   │   │   │   └── index.tsx
│   │   │   └── SonarrModal/
│   │   │       └── index.tsx
│   │   ├── Setup/
│   │   │   ├── JellyfinSetup.tsx
│   │   │   ├── LoginWithPlex.tsx
│   │   │   ├── SetupLogin.tsx
│   │   │   ├── SetupSteps.tsx
│   │   │   └── index.tsx
│   │   ├── Slider/
│   │   │   └── index.tsx
│   │   ├── StatusBadge/
│   │   │   └── index.tsx
│   │   ├── StatusChecker/
│   │   │   └── index.tsx
│   │   ├── TitleCard/
│   │   │   ├── ErrorCard.tsx
│   │   │   ├── Placeholder.tsx
│   │   │   ├── TmdbTitleCard.tsx
│   │   │   └── index.tsx
│   │   ├── Toast/
│   │   │   └── index.tsx
│   │   ├── ToastContainer/
│   │   │   └── index.tsx
│   │   ├── TvDetails/
│   │   │   ├── Season/
│   │   │   │   └── index.tsx
│   │   │   ├── TvCast/
│   │   │   │   └── index.tsx
│   │   │   ├── TvCrew/
│   │   │   │   └── index.tsx
│   │   │   ├── TvRecommendations.tsx
│   │   │   ├── TvSimilar.tsx
│   │   │   └── index.tsx
│   │   ├── UserList/
│   │   │   ├── BulkEditModal.tsx
│   │   │   ├── JellyfinImportModal.tsx
│   │   │   ├── PlexImportModal.tsx
│   │   │   └── index.tsx
│   │   └── UserProfile/
│   │       ├── ProfileHeader/
│   │       │   └── index.tsx
│   │       ├── UserSettings/
│   │       │   ├── UserGeneralSettings/
│   │       │   │   └── index.tsx
│   │       │   ├── UserLinkedAccountsSettings/
│   │       │   │   ├── LinkJellyfinModal.tsx
│   │       │   │   └── index.tsx
│   │       │   ├── UserNotificationSettings/
│   │       │   │   ├── UserNotificationsDiscord.tsx
│   │       │   │   ├── UserNotificationsEmail.tsx
│   │       │   │   ├── UserNotificationsPushbullet.tsx
│   │       │   │   ├── UserNotificationsPushover.tsx
│   │       │   │   ├── UserNotificationsTelegram.tsx
│   │       │   │   ├── UserNotificationsWebPush/
│   │       │   │   │   ├── DeviceItem.tsx
│   │       │   │   │   └── index.tsx
│   │       │   │   └── index.tsx
│   │       │   ├── UserPasswordChange/
│   │       │   │   └── index.tsx
│   │       │   ├── UserPermissions/
│   │       │   │   └── index.tsx
│   │       │   └── index.tsx
│   │       └── index.tsx
│   ├── context/
│   │   ├── InteractionContext.tsx
│   │   ├── LanguageContext.tsx
│   │   ├── SettingsContext.tsx
│   │   └── UserContext.tsx
│   ├── hooks/
│   │   ├── useClickOutside.ts
│   │   ├── useDebouncedState.ts
│   │   ├── useDeepLinks.ts
│   │   ├── useDiscover.ts
│   │   ├── useInteraction.ts
│   │   ├── useIsTouch.ts
│   │   ├── useLocale.ts
│   │   ├── useLockBodyScroll.ts
│   │   ├── usePlexLogin.ts
│   │   ├── useRequestOverride.ts
│   │   ├── useRouteGuard.ts
│   │   ├── useSearchInput.ts
│   │   ├── useSettings.ts
│   │   ├── useUpdateQueryParams.ts
│   │   ├── useUser.ts
│   │   └── useVerticalScroll.ts
│   ├── i18n/
│   │   ├── extractMessages.ts
│   │   ├── globalMessages.ts
│   │   └── locale/
│   │       ├── ar.json
│   │       ├── bg.json
│   │       ├── ca.json
│   │       ├── cs.json
│   │       ├── da.json
│   │       ├── de.json
│   │       ├── el.json
│   │       ├── en.json
│   │       ├── es.json
│   │       ├── es_MX.json
│   │       ├── et.json
│   │       ├── eu.json
│   │       ├── fi.json
│   │       ├── fr.json
│   │       ├── he.json
│   │       ├── hi.json
│   │       ├── hr.json
│   │       ├── hu.json
│   │       ├── it.json
│   │       ├── ja.json
│   │       ├── ko.json
│   │       ├── lb.json
│   │       ├── lt.json
│   │       ├── nb_NO.json
│   │       ├── nl.json
│   │       ├── pl.json
│   │       ├── pt_BR.json
│   │       ├── pt_PT.json
│   │       ├── ro.json
│   │       ├── ru.json
│   │       ├── sk.json
│   │       ├── sl.json
│   │       ├── sq.json
│   │       ├── sr.json
│   │       ├── sv.json
│   │       ├── tr.json
│   │       ├── uk.json
│   │       ├── vi.json
│   │       ├── zh_Hans.json
│   │       └── zh_Hant.json
│   ├── pages/
│   │   ├── 404.tsx
│   │   ├── _app.tsx
│   │   ├── _document.tsx
│   │   ├── _error.tsx
│   │   ├── blocklist/
│   │   │   └── index.tsx
│   │   ├── collection/
│   │   │   └── [collectionId]/
│   │   │       └── index.tsx
│   │   ├── discover/
│   │   │   ├── movies/
│   │   │   │   ├── genre/
│   │   │   │   │   └── [genreId]/
│   │   │   │   │       └── index.tsx
│   │   │   │   ├── genres.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   ├── keyword/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── language/
│   │   │   │   │   └── [language]/
│   │   │   │   │       └── index.tsx
│   │   │   │   ├── studio/
│   │   │   │   │   └── [studioId]/
│   │   │   │   │       └── index.tsx
│   │   │   │   └── upcoming.tsx
│   │   │   ├── trending.tsx
│   │   │   ├── tv/
│   │   │   │   ├── genre/
│   │   │   │   │   └── [genreId]/
│   │   │   │   │       └── index.tsx
│   │   │   │   ├── genres.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   ├── keyword/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── language/
│   │   │   │   │   └── [language]/
│   │   │   │   │       └── index.tsx
│   │   │   │   ├── network/
│   │   │   │   │   └── [networkId]/
│   │   │   │   │       └── index.tsx
│   │   │   │   └── upcoming.tsx
│   │   │   └── watchlist.tsx
│   │   ├── index.tsx
│   │   ├── issues/
│   │   │   ├── [issueId]/
│   │   │   │   └── index.tsx
│   │   │   └── index.tsx
│   │   ├── login/
│   │   │   ├── index.tsx
│   │   │   └── plex/
│   │   │       └── loading.tsx
│   │   ├── movie/
│   │   │   └── [movieId]/
│   │   │       ├── cast.tsx
│   │   │       ├── crew.tsx
│   │   │       ├── index.tsx
│   │   │       ├── recommendations.tsx
│   │   │       └── similar.tsx
│   │   ├── person/
│   │   │   └── [personId]/
│   │   │       └── index.tsx
│   │   ├── profile/
│   │   │   ├── index.tsx
│   │   │   ├── settings/
│   │   │   │   ├── index.tsx
│   │   │   │   ├── linked-accounts.tsx
│   │   │   │   ├── main.tsx
│   │   │   │   ├── notifications/
│   │   │   │   │   ├── discord.tsx
│   │   │   │   │   ├── email.tsx
│   │   │   │   │   ├── pushbullet.tsx
│   │   │   │   │   ├── pushover.tsx
│   │   │   │   │   ├── telegram.tsx
│   │   │   │   │   └── webpush.tsx
│   │   │   │   ├── password.tsx
│   │   │   │   └── permissions.tsx
│   │   │   └── watchlist.tsx
│   │   ├── requests/
│   │   │   └── index.tsx
│   │   ├── resetpassword/
│   │   │   ├── [guid]/
│   │   │   │   └── index.tsx
│   │   │   └── index.tsx
│   │   ├── search.tsx
│   │   ├── settings/
│   │   │   ├── about.tsx
│   │   │   ├── index.tsx
│   │   │   ├── jellyfin.tsx
│   │   │   ├── jobs.tsx
│   │   │   ├── logs.tsx
│   │   │   ├── main.tsx
│   │   │   ├── metadata.tsx
│   │   │   ├── network.tsx
│   │   │   ├── notifications/
│   │   │   │   ├── discord.tsx
│   │   │   │   ├── email.tsx
│   │   │   │   ├── gotify.tsx
│   │   │   │   ├── ntfy.tsx
│   │   │   │   ├── pushbullet.tsx
│   │   │   │   ├── pushover.tsx
│   │   │   │   ├── slack.tsx
│   │   │   │   ├── telegram.tsx
│   │   │   │   ├── webhook.tsx
│   │   │   │   └── webpush.tsx
│   │   │   ├── plex.tsx
│   │   │   ├── services.tsx
│   │   │   └── users.tsx
│   │   ├── setup.tsx
│   │   ├── tv/
│   │   │   └── [tvId]/
│   │   │       ├── cast.tsx
│   │   │       ├── crew.tsx
│   │   │       ├── index.tsx
│   │   │       ├── recommendations.tsx
│   │   │       └── similar.tsx
│   │   └── users/
│   │       ├── [userId]/
│   │       │   ├── index.tsx
│   │       │   ├── requests.tsx
│   │       │   ├── settings/
│   │       │   │   ├── index.tsx
│   │       │   │   ├── linked-accounts.tsx
│   │       │   │   ├── main.tsx
│   │       │   │   ├── notifications/
│   │       │   │   │   ├── discord.tsx
│   │       │   │   │   ├── email.tsx
│   │       │   │   │   ├── pushbullet.tsx
│   │       │   │   │   ├── pushover.tsx
│   │       │   │   │   ├── telegram.tsx
│   │       │   │   │   └── webpush.tsx
│   │       │   │   ├── password.tsx
│   │       │   │   └── permissions.tsx
│   │       │   └── watchlist.tsx
│   │       └── index.tsx
│   ├── styles/
│   │   └── globals.css
│   ├── types/
│   │   ├── custom.d.ts
│   │   └── react-intl-auto.d.ts
│   └── utils/
│       ├── creditHelpers.ts
│       ├── defineMessages.ts
│       ├── jellyfin.ts
│       ├── numberHelpers.ts
│       ├── plex.ts
│       ├── polyfillIntl.ts
│       ├── pushSubscriptionHelpers.ts
│       ├── refreshIntervalHelper.ts
│       ├── typeHelpers.ts
│       └── urlValidationHelper.ts
├── stylelint.config.js
├── tailwind.config.js
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
**/*.md
**/.gitkeep
**/.vscode
.dockerignore
.editorconfig
.eslintrc.js
.git
.gitconfig
.github
.gitignore
.husky
.next
.prettierignore
.vscode
charts
config/db/*
config/logs/*
config/*.json
cypress
dist
Dockerfile*
compose.yaml
gen-docs
docs
LICENSE
node_modules
public/os_logo_filled.png
public/preview.jpg
stylelint.config.js


================================================
FILE: .editorconfig
================================================
# editorconfig.org

root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

================================================
FILE: .gitattributes
================================================
* text eol=lf

#
## These files are binary and should be left untouched
#

# (binary is a macro for -text -diff)
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.mov binary
*.mp4 binary
*.mp3 binary
*.flv binary
*.fla binary
*.swf binary
*.gz binary
*.zip binary
*.7z binary
*.ttf binary
*.eot binary
*.woff binary
*.pyc binary
*.pdf binary

#
## Theses files/directories should be excluded from git archives
#

.husky export-ignore
.vscode export-ignore
docs export-ignore

.git* export-ignore
*ignore export-ignore
*.md export-ignore

.editorconfig export-ignore
Dockerfile.local export-ignore
compose.yaml export-ignore
stylelint.config.js export-ignore

public/os_logo_filled.png export-ignore
public/preview.jpg export-ignore


================================================
FILE: .github/CODEOWNERS
================================================
# Global code ownership
*                               @seerr-team/seerr-core


================================================
FILE: .github/FUNDING.yml
================================================
open_collective: seerr


================================================
FILE: .github/ISSUE_TEMPLATE/bug.yml
================================================
name: 🐛 Bug Report
description: Report a problem
labels: ['awaiting triage']
type: bug
body:
  - type: markdown
    attributes:
      value: |
        Thanks for taking the time to fill out this bug report!

        Please note that we use GitHub issues exclusively for bug reports and feature requests. For support requests, please use our other support channels to get help.
  - type: textarea
    id: description
    attributes:
      label: Description
      description: Please provide a clear and concise description of the bug or issue.
    validations:
      required: true
  - type: input
    id: version
    attributes:
      label: Version
      description: What version of Seerr are you running? (You can find this in Settings → About → Version.)
    validations:
      required: true
  - type: textarea
    id: repro-steps
    attributes:
      label: Steps to Reproduce
      description: Please tell us how we can reproduce the undesired behavior.
      placeholder: |
        1. Go to [...]
        2. Click on [...]
        3. Scroll down to [...]
        4. See error in [...]
    validations:
      required: true
  - type: textarea
    id: screenshots
    attributes:
      label: Screenshots
      description: If applicable, please provide screenshots depicting the problem.
  - type: textarea
    id: logs
    attributes:
      label: Logs
      description: Please copy and paste any relevant log output. (This will be automatically formatted into code, so no need for backticks.)
      render: shell
  - type: dropdown
    id: platform
    attributes:
      label: Platform
      options:
        - desktop
        - smartphone
        - tablet
    validations:
      required: true
  - type: dropdown
    id: database
    attributes:
      options:
        - SQLite (default)
        - PostgreSQL
      label: Database
      description: Which database backend are you using?
    validations:
      required: true
  - type: input
    id: device
    attributes:
      label: Device
      description: e.g., iPhone X, Surface Pro, Samsung Galaxy Tab
    validations:
      required: true
  - type: input
    id: os
    attributes:
      label: Operating System
      description: e.g., iOS 8.1, Windows 10, Android 11
    validations:
      required: true
  - type: input
    id: browser
    attributes:
      label: Browser
      description: e.g., Chrome, Safari, Edge, Firefox
    validations:
      required: true
  - type: textarea
    id: additional-context
    attributes:
      label: Additional Context
      description: Please provide any additional information that may be relevant or helpful.
  - type: checkboxes
    id: search-existing
    attributes:
      label: Search Existing Issues
      description: Have you searched existing issues to see if this bug has already been reported?
      options:
        - label: Yes, I have searched existing issues.
          required: true
  - type: checkboxes
    id: terms
    attributes:
      label: Code of Conduct
      description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/seerr-team/seerr/blob/develop/CODE_OF_CONDUCT.md)
      options:
        - label: I agree to follow Seerr's Code of Conduct
          required: true


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: 💬 Support via Discord
    url: https://discord.gg/seerr
    about: Chat with other users and the Seerr dev team
  - name: 💬 Support via GitHub Discussions
    url: https://github.com/seerr-team/seerr/discussions
    about: Ask questions and discuss with other community members


================================================
FILE: .github/ISSUE_TEMPLATE/documentation.yml
================================================
name: 📚 Documentation
description: Report a docs problem or suggest a docs improvement
title: "[Docs]: "
labels: ["documentation", "awaiting triage"]
type: task
body:
  - type: markdown
    attributes:
      value: |
        Thanks for helping improve the docs!

        Use this template for documentation issues (typos, unclear steps, missing info, outdated screenshots).
        For app bugs or feature ideas, please use the other templates.
  - type: input
    id: doc-location
    attributes:
      label: Page / Location
      description: Link to the docs page or the file/path (e.g. https://docs.seerr.dev/... or README.md)
      placeholder: "https://docs.seerr.dev/..."
    validations:
      required: true
  - type: dropdown
    id: doc-area
    attributes:
      label: Docs Area
      options:
        - docs site
        - migration guide
        - README / repo docs
        - API / integrations
        - other
    validations:
      required: true
  - type: textarea
    id: problem
    attributes:
      label: What’s wrong / missing?
      description: Describe the issue in the docs.
    validations:
      required: true
  - type: textarea
    id: suggested-fix
    attributes:
      label: Suggested change
      description: If you know what should be changed, describe it (or paste proposed wording).
    validations:
      required: false
  - type: checkboxes
    id: search-existing
    attributes:
      label: Search Existing Issues
      description: Have you searched existing issues to see if this has already been reported?
      options:
        - label: Yes, I have searched existing issues.
          required: true
  - type: checkboxes
    id: terms
    attributes:
      label: Code of Conduct
      description: By submitting this issue, you agree to follow our Code of Conduct.
      options:
        - label: I agree to follow Seerr's [Code of Conduct](https://github.com/seerr-team/seerr/blob/develop/CODE_OF_CONDUCT.md).
          required: true


================================================
FILE: .github/ISSUE_TEMPLATE/enhancement.yml
================================================
name: ✨ Feature Request
description: Suggest an idea
labels: ['awaiting triage']
type: feature
body:
  - type: markdown
    attributes:
      value: |
        Thanks for taking the time to fill out this feature request!

        Please note that we use GitHub issues exclusively for bug reports and feature requests. For support requests, please use our other support channels to get help.
  - type: textarea
    id: description
    attributes:
      label: Description
      description: Is your feature request related to a problem? If so, please provide a clear and concise description of the problem; e.g., "I'm always frustrated when [...]."
    validations:
      required: true
  - type: textarea
    id: desired-behavior
    attributes:
      label: Desired Behavior
      description: Provide a clear and concise description of what you want to happen.
    validations:
      required: true
  - type: textarea
    id: additional-context
    attributes:
      label: Additional Context
      description: Provide any additional information or screenshots that may be relevant or helpful.
  - type: checkboxes
    id: search-existing
    attributes:
      label: Search Existing Issues
      description: Have you searched existing issues to see if this feature has already been requested?
      options:
        - label: Yes, I have searched existing issues.
          required: true
  - type: checkboxes
    id: terms
    attributes:
      label: Code of Conduct
      description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/seerr-team/seerr/blob/develop/CODE_OF_CONDUCT.md)
      options:
        - label: I agree to follow Seerr's Code of Conduct
          required: true


================================================
FILE: .github/ISSUE_TEMPLATE/maintenance.yml
================================================
name: 🧰 Maintenance / Chore
description: CI, GitHub Actions, build, dependencies, refactors (non-feature work)
title: "[Chore]: "
labels: ["maintenance", "awaiting triage"]
type: task
body:
  - type: markdown
    attributes:
      value: |
        Maintainers / contributors: use this for internal tasks (CI, workflows, tooling, refactors).
        If you're reporting a user-facing bug or requesting a feature, use the other templates.
  - type: dropdown
    id: area
    attributes:
      label: Area
      options:
        - CI / GitHub Actions
        - build / packaging
        - dependencies
        - release process
        - refactor / tech debt
        - tooling / scripts
        - other
    validations:
      required: true
  - type: textarea
    id: summary
    attributes:
      label: Summary
      description: What needs doing and why?
    validations:
      required: true
  - type: textarea
    id: acceptance
    attributes:
      label: Acceptance criteria
      description: What does "done" look like?
      placeholder: |
        - [ ] ...
        - [ ] ...
    validations:
      required: false
  - type: input
    id: related
    attributes:
      label: Related links
      description: PRs, failing workflow runs, logs, or relevant issues.
    validations:
      required: false
  - type: checkboxes
    id: search-existing
    attributes:
      label: Search Existing Issues
      description: Have you searched existing issues to see if this has already been reported?
      options:
        - label: Yes, I have searched existing issues.
          required: true
  - type: checkboxes
    id: terms
    attributes:
      label: Code of Conduct
      description: By submitting this issue, you agree to follow our Code of Conduct.
      options:
        - label: I agree to follow Seerr's [Code of Conduct](https://github.com/seerr-team/seerr/blob/develop/CODE_OF_CONDUCT.md).
          required: true


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--
    Please read contributing guide before submitting
    your pull request. Please fill in each section below to help us better prioritize your pull request. Thanks!
-->

## Description

<!--- Describe your changes in detail -->
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

- Fixes #XXXX

## How Has This Been Tested?

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->

## Screenshots / Logs (if applicable)

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [ ] I have read and followed the contribution [guidelines](https://github.com/seerr-team/seerr/blob/develop/CONTRIBUTING.md).
- [ ] Disclosed any use of AI (see our [policy](https://github.com/seerr-team/seerr/blob/develop/CONTRIBUTING.md#ai-assistance-notice))
- [ ] I have updated the documentation accordingly.
- [ ] All new and existing tests passed.
- [ ] Successful build `pnpm build`
- [ ] Translation keys `pnpm i18n:extract`
- [ ] Database migration (if required)


================================================
FILE: .github/cliff.toml
================================================
# git-cliff ~ configuration
# https://git-cliff.org/docs/configuration

[changelog]
header = ""
body = """
{%- macro remote_url() -%}
  https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
{%- endmacro -%}

{%- set excluded_users = ["github-actions[bot]", "dependabot[bot]", "renovate[bot]"] -%}

{% macro print_commit(commit) -%}
    - {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
        {% if commit.breaking %}[**breaking**] {% endif %}\
        {{ commit.message | upper_first }} - \
        ([{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}))\
{% endmacro -%}

{% if version %}\
    {% if previous.version %}\
        ## [{{ version | trim_start_matches(pat="v") }}]({{ self::remote_url() }}/compare/{{ previous.version }}..{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }}
    {% else %}\
        ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
    {% endif %}\
{% else %}\
    ## [unreleased]
{% endif %}\

{%- for group, commits in commits | group_by(attribute="group") %}
    ### {{ group | striptags | trim | upper_first }}
    {%- for commit in commits | filter(attribute="scope") | sort(attribute="scope") %}
        {{ self::print_commit(commit=commit) }}
    {%- endfor %}
    {%- for commit in commits %}
        {%- if not commit.scope %}
            {{ self::print_commit(commit=commit) }}
        {%- endif %}
    {%- endfor -%}
{%- endfor -%}

{%- set valid_contributors = [] -%}
{%- for c in github.contributors | filter(attribute="is_first_time", value=true) %}
  {%- if c.username and c.username not in excluded_users and c.username not in valid_contributors %}
    {%- set_global valid_contributors = valid_contributors | concat(with=c.username) %}
  {%- endif %}
{%- endfor %}

{%- if valid_contributors | length > 0 %}
## New Contributors ❤️
  {%- for username in valid_contributors %}
* @{{ username }} made their first contribution
  {%- endfor %}
{%- endif %}
"""
footer = """
<!-- generated by git-cliff -->
"""
trim = true
postprocessors = []

[git]
conventional_commits = true
filter_unconventional = true
split_commits = false
filter_commits = true
commit_preprocessors = [
  { pattern = '.*\[skip ci\].*', replace = "" },
  { pattern = '.*\[ci skip\].*', replace = "" },
]
commit_parsers = [
  { message = '.*\(helm\).*', skip = true },
  { message = '^chore\(release\): prepare for', skip = true },
  { message = '^chore\(deps.*\)', skip = true },
  { body = ".*security", group = "<!-- 0 -->🛡️ Security" },
  { message = "^feat", group = "<!-- 1 -->🚀 Features" },
  { message = "^fix", group = "<!-- 2 -->🐛 Bug Fixes" },
  { message = "^doc", group = "<!-- 3 -->📖 Documentation" },
  { message = "^perf", group = "<!-- 4 -->⚡ Performance" },
  { message = "^refactor", group = "<!-- 5 -->🚜 Refactor" },
  { message = "^style", group = "<!-- 6 -->🎨 Styling" },
  { message = "^test", group = "<!-- 7 -->🧪 Testing" },
  { message = "^chore|^ci", group = "<!-- 8 -->⚙️ Miscellaneous Tasks" },
  { message = "^revert", group = "<!-- 9 -->◀️ Revert" },
]
protect_breaking_commits = false
tag_pattern = "v?[0-9]+\\.[0-9]+\\.[0-9]+.*"
skip_tags = "beta|alpha|rc"
topo_order = false
sort_commits = "newest"


================================================
FILE: .github/renovate/actions.json5
================================================
{
  $schema: 'https://docs.renovatebot.com/renovate-schema.json',

  extends: ['helpers:pinGitHubActionDigests'],

  packageRules: [
    // All GitHub Actions need manual review
    {
      matchManagers: ['github-actions'],
      groupName: 'GitHub Actions',
    },
  ],
}


================================================
FILE: .github/renovate/docker.json5
================================================
{
  $schema: 'https://docs.renovatebot.com/renovate-schema.json',

  extends: [
    'docker:enableMajor',
    'docker:pinDigests'
  ],

  packageRules: [
    {
      matchManagers: ['docker-compose'],
      pinDigests: false,
    },
  ],
}


================================================
FILE: .github/renovate/groups.json5
================================================
{
  $schema: 'https://docs.renovatebot.com/renovate-schema.json',

  packageRules: [
    // Node.js
    {
      matchPackageNames: ['node'],
      matchManagers: ['dockerfile', 'npm'],
      groupName: 'Node.js',
      commitMessageTopic: 'Node.js',
    },

    // Database packages
    {
      matchPackageNames: ['pg', 'sqlite3', 'typeorm'],
      groupName: 'Database',
    },
  ],
}


================================================
FILE: .github/renovate/helm.json5
================================================
{
  $schema: 'https://docs.renovatebot.com/renovate-schema.json',

  packageRules: [
    {
      matchManagers: ['helm-values'],
      matchFileNames: ['charts/*/values.yaml'],
      minimumReleaseAge: '0',
      pinDigests: false,
    },
  ],

  customManagers: [
    {
      customType: 'regex',
      description: 'Update appVersion in Chart.yaml to match Docker image',
      fileMatch: ['(^|/)Chart\\.yaml$'],
      matchStrings: [
        "#\\s+renovate:\\s+image=(?<depName>\\S*)\nappVersion:\\s+'(?<currentValue>\\S*)'",
      ],
      datasourceTemplate: 'docker',
    },
  ],
}


================================================
FILE: .github/renovate/labels.json5
================================================
{
  $schema: 'https://docs.renovatebot.com/renovate-schema.json',

  packageRules: [
    // JavaScript/npm packages
    {
      matchManagers: ['npm'],
      addLabels: ['javascript'],
    },

    // GitHub Actions
    {
      matchManagers: ['github-actions'],
      addLabels: ['github_actions'],
    },

    // Docker images
    {
      matchManagers: ['dockerfile', 'docker-compose'],
      addLabels: ['docker'],
    },

    // Helm charts
    {
      matchManagers: ['helm-values'],
      addLabels: ['helm'],
    },
  ],
}


================================================
FILE: .github/renovate/pnpm.json5
================================================
{
  $schema: 'https://docs.renovatebot.com/renovate-schema.json',

  // Run pnpm dedupe after dependency updates
  postUpdateOptions: ['pnpmDedupe'],

  lockFileMaintenance: {
    enabled: true,
  },

}


================================================
FILE: .github/renovate/semanticCommits.json5
================================================
{
  $schema: 'https://docs.renovatebot.com/renovate-schema.json',

  packageRules: [
    // Default for all dependencies
    {
      matchPackagePatterns: ['*'],
      semanticCommitType: 'chore',
      semanticCommitScope: 'deps',
    },

    // Node.js runtime
    {
      matchPackageNames: ['node'],
      semanticCommitType: 'build',
      semanticCommitScope: 'node',
    },

    // GitHub Actions
    {
      matchManagers: ['github-actions'],
      semanticCommitType: 'ci',
      semanticCommitScope: 'actions',
    },

    // Docker
    {
      matchManagers: ['dockerfile'],
      semanticCommitType: 'build',
      semanticCommitScope: 'docker',
    },
  ],
}


================================================
FILE: .github/renovate.json5
================================================
{
  $schema: 'https://docs.renovatebot.com/renovate-schema.json',
  extends: [
    'config:recommended',
    ':dependencyDashboard',
    ':timezone(UTC)',
    'group:allNonMajor',
    'group:nextjsMonorepo',
    'group:reactMonorepo',
    'group:typescript-eslintMonorepo',
    'group:tailwindcssMonorepo',
    'github>seerr-team/seerr//.github/renovate/actions.json5',
    'github>seerr-team/seerr//.github/renovate/docker.json5',
    'github>seerr-team/seerr//.github/renovate/groups.json5',
    'github>seerr-team/seerr//.github/renovate/helm.json5',
    'github>seerr-team/seerr//.github/renovate/labels.json5',
    'github>seerr-team/seerr//.github/renovate/pnpm.json5',
    'github>seerr-team/seerr//.github/renovate/semanticCommits.json5',
  ],
  dependencyDashboardTitle: 'Renovate Dashboard 🤖',
  suppressNotifications: ['prEditedNotification', 'prIgnoreNotification'],
  rebaseWhen: 'conflicted',
  labels: ['dependencies'],
  minimumReleaseAge: '7 days',
}


================================================
FILE: .github/workflows/ci.yml
================================================
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Seerr CI

on:
  pull_request:
    branches:
      - '*'
  push:
    branches:
      - develop
  workflow_dispatch:

permissions:
  contents: read

env:
  DOCKER_HUB: seerr/seerr

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  i18n:
    name: i18n Check
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-24.04
    permissions:
      contents: read
      pull-requests: write
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GH_REPO: ${{ github.repository }}
      NUMBER: ${{ github.event.pull_request.number }}
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          persist-credentials: false

      - name: Pnpm Setup
        uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0

      - name: Set up Node.js
        uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
        with:
          node-version-file: 'package.json'

      - name: Get pnpm store directory
        shell: bash
        run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

      - name: Setup pnpm cache
        uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
        with:
          path: ${{ env.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - name: Install dependencies
        env:
          CI: true
        run: pnpm install

      - name: i18n Check
        shell: bash
        env:
          I18N_LABEL: i18n-out-of-sync
          BODY: |
            The i18n check failed because translation messages are out of sync.

            This usually happens when you've added or modified translation strings in your code but haven't updated the translation file.

            Please run `pnpm i18n:extract` and commit the changes.
        run: |
          retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; }
          check_failed=0; node bin/check-i18n.js || check_failed=$?
          pr_labels=$(gh pr view "$NUMBER" -R "$GH_REPO" --json labels -q '.labels[].name' 2>/dev/null) || true
          has_label=0
          while IFS= read -r name; do [ -n "$name" ] && [ "$name" = "$I18N_LABEL" ] && has_label=1 && break; done <<< "$pr_labels"
          if [ "$check_failed" -eq 1 ]; then
            [ "$has_label" -eq 0 ] && { retry gh pr edit "$NUMBER" -R "$GH_REPO" --add-label "$I18N_LABEL" || true; retry gh pr comment "$NUMBER" -R "$GH_REPO" -b "$BODY" || true; }
          else
            [ "$has_label" -eq 1 ] && retry gh pr edit "$NUMBER" -R "$GH_REPO" --remove-label "$I18N_LABEL" || true
          fi
          exit $check_failed
  test:
    name: Lint & Test Build
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-24.04
    container: node:22.22.1-alpine3.22@sha256:9f96f09f127f06feaff1e7faa4a34a3020cf5c1138c988782e59959641facabe
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          persist-credentials: false

      - name: Pnpm Setup
        uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0

      - name: Get pnpm store directory
        shell: sh
        run: |
          echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

      - name: Setup pnpm cache
        uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
        with:
          path: ${{ env.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - name: Install dependencies
        env:
          CI: true
        run: pnpm install

      - name: Lint
        run: pnpm lint

      - name: Formatting
        run: pnpm format:check

      - name: Build
        run: pnpm build

  unit-test:
    name: Unit Tests
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-24.04
    container: node:22.22.1-alpine3.22@sha256:9f96f09f127f06feaff1e7faa4a34a3020cf5c1138c988782e59959641facabe
    permissions:
      checks: write
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          persist-credentials: false

      - name: Pnpm Setup
        uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0

      - name: Get pnpm store directory
        shell: sh
        run: |
          echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

      - name: Setup pnpm cache
        uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
        with:
          path: ${{ env.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - name: Install dependencies
        env:
          CI: true
        run: pnpm install

      - name: Run tests
        env:
          CI: true
        run: pnpm test

      - name: Publish test report
        uses: mikepenz/action-junit-report@49b2ca06f62aa7ef83ae6769a2179271e160d8e4 # v6.3.1
        if: success() || failure() # always run even if the previous step fails
        with:
          report_paths: 'report.xml'

  build:
    name: Build (per-arch, native runners)
    if: github.ref == 'refs/heads/develop'
    strategy:
      matrix:
        include:
          - runner: ubuntu-24.04
            platform: linux/amd64
            arch: amd64
          - runner: ubuntu-24.04-arm
            platform: linux/arm64
            arch: arm64
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          persist-credentials: false

      - name: Commit timestamp
        id: ts
        run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> "$GITHUB_OUTPUT"

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0

      - name: Warm cache (no push) — ${{ matrix.platform }}
        uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
        with:
          context: .
          file: ./Dockerfile
          platforms: ${{ matrix.platform }}
          push: false
          build-args: |
            COMMIT_TAG=${{ github.sha }}
            BUILD_VERSION=develop
            SOURCE_DATE_EPOCH=${{ steps.ts.outputs.TIMESTAMP }}
          cache-from: type=gha,scope=${{ matrix.platform }}
          cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
          provenance: false

  publish:
    name: Publish multi-arch image
    needs: build
    runs-on: ubuntu-24.04
    permissions:
      contents: read
      packages: write
      id-token: write
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          persist-credentials: false

      - name: Commit timestamp
        id: ts
        run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> "$GITHUB_OUTPUT"

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0

      - name: Log in to Docker Hub
        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_TOKEN }}

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
        with:
          images: |
            ${{ env.DOCKER_HUB }}
            ghcr.io/${{ github.repository }}
          tags: |
            type=raw,value=develop
            type=sha
          labels: |
            org.opencontainers.image.created=${{ steps.ts.outputs.TIMESTAMP }}

      - name: Build & Push (multi-arch, single tag)
        uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
        with:
          context: .
          file: ./Dockerfile
          platforms: linux/amd64,linux/arm64
          push: true
          build-args: |
            COMMIT_TAG=${{ github.sha }}
            BUILD_VERSION=develop
            SOURCE_DATE_EPOCH=${{ steps.ts.outputs.TIMESTAMP }}
          labels: ${{ steps.meta.outputs.labels }}
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: |
            type=gha,scope=linux/amd64
            type=gha,scope=linux/arm64
          cache-to: type=gha,mode=max
          provenance: false

  discord:
    name: Send Discord Notification
    needs: publish
    if: always() && github.event_name != 'pull_request'
    runs-on: ubuntu-24.04
    steps:
      - name: Determine Workflow Status
        id: status
        run: |
          case "${{ needs.publish.result }}" in
            success)   echo "status=Success"   >> $GITHUB_OUTPUT; echo "colour=3066993"  >> $GITHUB_OUTPUT ;;
            failure)   echo "status=Failure"   >> $GITHUB_OUTPUT; echo "colour=15158332" >> $GITHUB_OUTPUT ;;
            cancelled) echo "status=Cancelled" >> $GITHUB_OUTPUT; echo "colour=10181046" >> $GITHUB_OUTPUT ;;
            *)         echo "status=Skipped"   >> $GITHUB_OUTPUT; echo "colour=9807270"  >> $GITHUB_OUTPUT ;;
          esac

      - name: Send Discord notification
        shell: bash
        run: |
          WEBHOOK="${{ secrets.DISCORD_WEBHOOK }}"

          PAYLOAD=$(cat <<EOF
          {
            "embeds": [{
              "title": "${{ steps.status.outputs.status }}: ${{ github.workflow }}",
              "color": ${{ steps.status.outputs.colour }},
              "fields": [
                { "name": "Repository",   "value": "[${{ github.repository }}](${{ github.server_url }}/${{ github.repository }})", "inline": true },
                { "name": "Ref",          "value": "${{ github.ref }}", "inline": true },
                { "name": "Event",        "value": "${{ github.event_name }}", "inline": true },
                { "name": "Triggered by", "value": "${{ github.actor }}", "inline": true },
                { "name": "Workflow",     "value": "[${{ github.workflow }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})", "inline": true }
              ],
            }]
          }
          EOF
          )

          curl -sS -H "Content-Type: application/json" -X POST -d "$PAYLOAD" "$WEBHOOK" || true


================================================
FILE: .github/workflows/codeql.yml
================================================
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: 'CodeQL'

on:
  push:
    branches: ['develop']
    paths-ignore:
      - '**/*.md'
      - 'docs/**'
  pull_request:
    branches: ['develop']
    paths-ignore:
      - '**/*.md'
      - 'docs/**'
  schedule:
    - cron: '50 7 * * 5'

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-24.04
    timeout-minutes: 10
    permissions:
      contents: read
      security-events: write
    strategy:
      fail-fast: false
      matrix:
        language: [actions, javascript]
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          persist-credentials: false

      - name: Initialize CodeQL
        uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
        with:
          languages: ${{ matrix.language }}
          queries: +security-and-quality

      - name: Autobuild
        uses: github/codeql-action/autobuild@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
        with:
          category: '/language:${{ matrix.language }}'


================================================
FILE: .github/workflows/conflict_labeler.yml
================================================
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Merge Conflict Labeler

on:
  push:
    branches: [develop]

  pull_request_target:
    branches: [develop]
    types: [opened, synchronize, reopened]

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  label:
    name: Labeling
    runs-on: ubuntu-24.04
    timeout-minutes: 10
    permissions:
      contents: read
      pull-requests: write
    steps:
      - name: Apply label
        uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3
        with:
          dirtyLabel: 'merge conflict'
          commentOnDirty: 'This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.'
          repoToken: '${{ secrets.GITHUB_TOKEN }}'


================================================
FILE: .github/workflows/create-tag.yml
================================================
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Create tag

on:
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  determine-tag-version:
    name: Determine tag version
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-24.04
    permissions:
      contents: read
    outputs:
      tag_version: ${{ steps.git-cliff.outputs.tag_version }}
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          fetch-depth: 0
          persist-credentials: false

      - name: Install git-cliff
        uses: taiki-e/install-action@a37010ded18ff788be4440302bd6830b1ae50d8b # v2.68.25
        with:
          tool: git-cliff

      - name: Get tag version
        id: git-cliff
        run: |
          tag_version=$(git-cliff -c .github/cliff.toml --bumped-version --unreleased)
          echo "Next tag version is ${tag_version}"
          echo "tag_version=${tag_version}" >> "$GITHUB_OUTPUT"

  create-tag:
    name: Create tag
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-24.04
    permissions:
      contents: write
    needs: determine-tag-version
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      TAG_VERSION: ${{ needs.determine-tag-version.outputs.tag_version }}
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          ssh-key: '${{ secrets.COMMIT_KEY }}'

      - name: Pnpm Setup
        uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0

      - name: Set up Node.js
        uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
        with:
          node-version-file: 'package.json'
          # For workflows with elevated privileges we recommend disabling automatic caching.
          # https://github.com/actions/setup-node
          package-manager-cache: false

      - name: Configure git
        run: |
          git config --global user.name "${{ github.actor }}"
          git config --global user.email "${{ github.actor }}@users.noreply.github.com"

      - name: Bump package.json
        run: npm version ${TAG_VERSION} --no-commit-hooks --no-git-tag-version

      - name: Commit updated files
        run: |
          git add package.json
          git commit -m "chore(release): prepare ${TAG_VERSION}"
          git push

      - name: Create git tag
        run: |
          git tag ${TAG_VERSION}
          git push origin ${TAG_VERSION}


================================================
FILE: .github/workflows/cypress.yml
================================================
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Cypress Tests

on:
  pull_request:
    branches: ['*']
    paths:
      - '{src,server,config,cypress}/**'
      - 'cypress.config.ts'
      - 'package.json'
      - 'pnpm-lock.yaml'
      - 'next.config.js'
      - 'tsconfig.json'
      - '.github/workflows/cypress.yml'
  push:
    branches: [develop]
    paths:
      - '{src,server,config,cypress}/**'
      - 'cypress.config.ts'
      - 'package.json'
      - 'pnpm-lock.yaml'
      - 'next.config.js'
      - 'tsconfig.json'
      - '.github/workflows/cypress.yml'

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  cypress-run:
    name: Cypress Run
    runs-on: ubuntu-24.04
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          persist-credentials: false

      - name: Set up Node.js
        uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
        with:
          node-version-file: package.json
          package-manager-cache: false

      - name: Pnpm Setup
        uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Setup cypress cache
        uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
        with:
          path: ~/.cache/Cypress
          key: ${{ runner.os }}-cypress-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-cypress-store-

      - name: Install Cypress binary
        env:
          CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
        run: pnpm exec cypress install

      - name: Cypress run
        uses: cypress-io/github-action@f790eee7a50d9505912f50c2095510be7de06aa7 # v6.10.9
        with:
          install: false
          build: pnpm cypress:build
          start: pnpm start
          wait-on: 'http://localhost:5055'
          record: true
        env:
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          WITH_MIGRATIONS: true
          # Fix test titles in cypress dashboard
          COMMIT_INFO_MESSAGE: ${{github.event.pull_request.title}}
          COMMIT_INFO_SHA: ${{github.event.pull_request.head.sha}}


================================================
FILE: .github/workflows/detect-duplicate.yml
================================================
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Duplicate Issue Detector

on:
  issues:
    types: [opened]

permissions: {}

env:
  EMBEDDING_MODEL: ${{ vars.EMBEDDING_MODEL }}
  GROQ_MODEL: ${{ vars.GROQ_MODEL }}
  GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}

jobs:
  detect-duplicate:
    runs-on: ubuntu-24.04
    if: ${{ !github.event.issue.pull_request }}
    permissions:
      issues: write
      actions: read
      contents: read

    steps:
      - name: Checkout repository
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1

      - name: Pnpm Setup
        uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0

      - name: Set up Node.js
        uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
        with:
          node-version-file: 'package.json'

      - name: Get pnpm store directory
        shell: bash
        run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

      - name: Setup pnpm cache
        uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
        with:
          path: ${{ env.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - name: Install dependencies
        working-directory: bin/duplicate-detector
        env:
          CI: true
        run: pnpm install --frozen-lockfile

      - name: Download issue index
        uses: dawidd6/action-download-artifact@5c98f0b039f36ef966fdb7dfa9779262785ecb05 # v14
        with:
          name: issue-index
          workflow: rebuild-issue-index.yml
          path: bin/duplicate-detector
          search_artifacts: true
          if_no_artifact_found: warn

      - name: Build index if missing
        working-directory: bin/duplicate-detector
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_REPOSITORY: ${{ github.repository }}
          INDEX_PATH: issue_index.json
        run: |
          if [ ! -f issue_index.json ]; then
            echo "No index found — building from scratch..."
            node build-index.mjs
          fi

      - name: Detect duplicates
        working-directory: bin/duplicate-detector
        continue-on-error: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_REPOSITORY: ${{ github.repository }}
          ISSUE_NUMBER: ${{ github.event.issue.number }}
          INDEX_PATH: issue_index.json
        run: node detect.mjs


================================================
FILE: .github/workflows/docs-deploy.yml
================================================
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Deploy to GitHub Pages

on:
  workflow_dispatch:
  push:
    branches:
      - develop
    paths:
      - 'docs/**'
      - 'gen-docs/**'

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build:
    name: Build Docusaurus
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          fetch-depth: 0
          persist-credentials: false

      - name: Set up Node.js
        uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
        with:
          node-version-file: package.json
          package-manager-cache: false

      - name: Pnpm Setup
        uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0

      - name: Get pnpm store directory
        shell: sh
        run: |
          echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

      - name: Setup pnpm cache
        uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
        with:
          path: ${{ env.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - name: Install dependencies
        run: |
          cd gen-docs
          pnpm install --frozen-lockfile

      - name: Build website
        working-directory: gen-docs
        run: pnpm build

      - name: Upload Build Artifact
        uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b6 # v4.0.0
        with:
          path: gen-docs/build

  deploy:
    name: Deploy to GitHub Pages
    needs: build
    runs-on: ubuntu-24.04
    permissions:
      contents: read
      pages: write
      id-token: write
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5


================================================
FILE: .github/workflows/docs-link-check.yml
================================================
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Check Docs Links

on:
  pull_request:
    branches:
      - '*'
    paths:
      - 'docs/**'
      - 'gen-docs/**'
      - '.github/workflows/docs-link-check.yml'
  push:
    branches:
      - develop
    paths:
      - 'docs/**'
      - 'gen-docs/**'
      - '.github/workflows/docs-link-check.yml'
  schedule:
    - cron: '50 7 * * 5'
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  link-check:
    name: Verify external links in Markdown and MDX
    runs-on: ubuntu-24.04
    timeout-minutes: 20

    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          fetch-depth: 0
          persist-credentials: false

      - name: Run Lychee link checker
        uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2.8.0
        with:
          fail: false
          args: >-
            --verbose
            --no-progress
            --accept 200..204,300..304,307,308,404,429,999
            --exclude '^file://'
            --exclude '^https?://(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\]|\[::\])'
            --exclude '^https?://support\.discord\.com'
            './docs/**/*.md'
            './docs/**/*.mdx'
            './gen-docs/**/*.md'
            './gen-docs/**/*.mdx'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Upload Lychee report
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: lychee-report
          path: |
            lychee/out.md
            lychee/results.json
          if-no-files-found: ignore


================================================
FILE: .github/workflows/helm.yml
================================================
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Release Charts

on:
  push:
    branches:
      - develop
    paths:
      - 'charts/**'
      - '.github/workflows/release-charts.yml'

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  package-helm-chart:
    name: Package helm chart
    runs-on: ubuntu-24.04
    permissions:
      contents: read
      packages: read
    outputs:
      has_artifacts: ${{ steps.check-artifacts.outputs.has_artifacts }}
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          fetch-depth: 0
          persist-credentials: false

      - name: Install helm
        uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1

      - name: Install Oras
        uses: oras-project/setup-oras@22ce207df3b08e061f537244349aac6ae1d214f6 # v1.2.4

      - name: Login to GitHub Container Registry
        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Package helm charts
        run: |
          mkdir -p ./.cr-release-packages
          for chart_path in ./charts/*; do
            if [ -d "$chart_path" ] && [ -f "$chart_path/Chart.yaml" ]; then
              chart_name=$(grep '^name:' "$chart_path/Chart.yaml" | awk '{print $2}')
              # get current version
              current_version=$(grep '^version:' "$chart_path/Chart.yaml" | awk '{print $2}')
              # try to get current release version
              if oras manifest fetch "ghcr.io/${{ github.repository }}/${chart_name}:${current_version}" >/dev/null 2>&1; then
                echo "No version change for $chart_name. Skipping."
              else
                helm dependency build "$chart_path"
                helm package "$chart_path" --destination ./.cr-release-packages
              fi
            else
              echo "Skipping $chart_name: Not a valid Helm chart"
            fi
          done

      - name: Check if artifacts exist
        id: check-artifacts
        run: |
          if ls .cr-release-packages/*.tgz >/dev/null 2>&1; then
            echo "has_artifacts=true" >> $GITHUB_OUTPUT
          else
            echo "has_artifacts=false" >> $GITHUB_OUTPUT
          fi

      - name: Upload artifacts
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        if: steps.check-artifacts.outputs.has_artifacts == 'true'
        with:
          name: artifacts
          include-hidden-files: true
          path: .cr-release-packages/

  publish:
    name: Publish to ghcr.io
    runs-on: ubuntu-24.04
    permissions:
      packages: write
      id-token: write
    needs: [package-helm-chart]
    if: needs.package-helm-chart.outputs.has_artifacts == 'true'
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          fetch-depth: 0
          persist-credentials: false

      - name: Install helm
        uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1

      - name: Install Oras
        uses: oras-project/setup-oras@22ce207df3b08e061f537244349aac6ae1d214f6 # v1.2.4

      - name: Install Cosign
        uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0

      - name: Downloads artifacts
        uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
        with:
          name: artifacts
          path: .cr-release-packages/

      - name: Login to GitHub Container Registry
        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Push charts to GHCR
        env:
          COSIGN_YES: true
        run: |
          for chart_path in `find .cr-release-packages -name '*.tgz' -print`; do
            # push chart to OCI
            chart_release_file=$(basename "$chart_path")
            chart_name=${chart_release_file%-*}
            helm push ${chart_path} oci://ghcr.io/${{ github.repository }} |& tee helm-push-output.log
            chart_digest=$(awk -F "[, ]+" '/Digest/{print $NF}' < helm-push-output.log)
            # sign chart
            cosign sign "ghcr.io/${{ github.repository }}/${chart_name}@${chart_digest}"
            # push artifacthub-repo.yml to OCI
            oras push \
              ghcr.io/${{ github.repository }}/${chart_name}:artifacthub.io \
              --config /dev/null:application/vnd.cncf.artifacthub.config.v1+yaml \
              charts/$chart_name/artifacthub-repo.yml:application/vnd.cncf.artifacthub.repository-metadata.layer.v1.yaml \
              |& tee oras-push-output.log
            artifacthub_digest=$(grep "Digest:" oras-push-output.log | awk '{print $2}')
            # sign artifacthub-repo.yml
            cosign sign "ghcr.io/${{ github.repository }}/${chart_name}:artifacthub.io@${artifacthub_digest}"
          done

  verify:
    name: Verify signatures for each chart tag
    needs: [publish]
    runs-on: ubuntu-24.04
    permissions:
      contents: read
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          fetch-depth: 0
          persist-credentials: false

      - name: Install Cosign
        uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0

      - name: Downloads artifacts
        uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
        with:
          name: artifacts
          path: .cr-release-packages/

      - name: Login to GitHub Container Registry
        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Verify signatures for each chart tag
        run: |
          for chart_path in $(find .cr-release-packages -name '*.tgz' -print); do
            chart_release_file=$(basename "$chart_path")
            chart_name=${chart_release_file%-*}
            version=${chart_release_file#$chart_name-}
            version=${version%.tgz}

            cosign verify "ghcr.io/${{ github.repository }}/${chart_name}:${version}" \
              --certificate-identity "https://github.com/${{ github.workflow_ref }}" \
              --certificate-oidc-issuer "https://token.actions.githubusercontent.com"
          done


================================================
FILE: .github/workflows/lint-helm-charts.yml
================================================
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Lint and Test Charts

on:
  pull_request:
    branches:
      - develop
    paths:
      - '.github/workflows/lint-helm-charts.yml'
      - 'charts/**'
  push:
    branches: [develop]
    paths:
      - 'charts/**'

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  lint-test:
    runs-on: ubuntu-24.04
    permissions:
      contents: read
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          fetch-depth: 0
          persist-credentials: false

      - name: Set up Helm
        uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1

      - name: Set up chart-testing
        uses: helm/chart-testing-action@6ec842c01de15ebb84c8627d2744a0c2f2755c9f # v2.8.0

      - name: Ensure documentation is updated
        uses: docker://jnorwood/helm-docs:v1.14.2@sha256:7e562b49ab6b1dbc50c3da8f2dd6ffa8a5c6bba327b1c6335cc15ce29267979c

      - name: Run chart-testing (list-changed)
        id: list-changed
        run: |
          changed=$(ct list-changed --target-branch ${{ github.event.repository.default_branch }})
          if [[ -n "$changed" ]]; then
            echo "changed=true" >> "$GITHUB_OUTPUT"
            echo "$changed"
          fi

      - name: Run chart-testing
        if: steps.list-changed.outputs.changed == 'true'
        run: ct lint --target-branch ${{ github.event.repository.default_branch }} --validate-maintainers=false


================================================
FILE: .github/workflows/preview.yml
================================================
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Seerr Preview

on:
  push:
    tags:
      - 'preview-*'
  workflow_dispatch:

permissions:
  contents: read

env:
  DOCKER_HUB: seerr/seerr

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build:
    name: Build (per-arch, native runners)
    strategy:
      matrix:
        include:
          - runner: ubuntu-24.04
            platform: linux/amd64
            arch: amd64
          - runner: ubuntu-24.04-arm
            platform: linux/arm64
            arch: arm64
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          persist-credentials: false

      - name: Commit timestamp
        id: ts
        run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> "$GITHUB_OUTPUT"

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0

      - name: Derive preview version from tag
        id: ver
        shell: bash
        run: |
          TAG="${GITHUB_REF_NAME}"
          VER="${TAG#preview-}"
          VER="${VER#v}"
          echo "version=${VER}" >> "$GITHUB_OUTPUT"
          echo "Building preview version: ${VER}"

      - name: Warm cache (no push) — ${{ matrix.platform }}
        uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
        with:
          context: .
          file: ./Dockerfile
          platforms: ${{ matrix.platform }}
          push: false
          build-args: |
            COMMIT_TAG=${{ github.sha }}
            BUILD_VERSION=${{ steps.ver.outputs.version }}
            SOURCE_DATE_EPOCH=${{ steps.ts.outputs.TIMESTAMP }}
          cache-from: type=gha,scope=${{ matrix.platform }}
          cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
          provenance: false

  publish:
    name: Publish multi-arch image
    needs: build
    runs-on: ubuntu-24.04
    permissions:
      contents: read
      packages: write
      id-token: write
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          persist-credentials: false

      - name: Commit timestamp
        id: ts
        run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> "$GITHUB_OUTPUT"

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0

      - name: Log in to Docker Hub
        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_TOKEN }}

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Derive preview version from tag
        id: ver
        shell: bash
        run: |
          TAG="${GITHUB_REF_NAME}"
          VER="${TAG#preview-}"
          VER="${VER#v}"
          echo "version=${VER}" >> "$GITHUB_OUTPUT"
          echo "Publishing preview version: ${VER}"

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
        with:
          images: |
            ${{ env.DOCKER_HUB }}
            ghcr.io/${{ github.repository }}
          tags: |
            type=raw,value=preview-${{ steps.ver.outputs.version }}
          labels: |
            org.opencontainers.image.version=preview-${{ steps.ver.outputs.version }}
            org.opencontainers.image.created=${{ steps.ts.outputs.TIMESTAMP }}

      - name: Build & Push (multi-arch, single tag)
        uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
        with:
          context: .
          file: ./Dockerfile
          platforms: linux/amd64,linux/arm64
          push: true
          build-args: |
            COMMIT_TAG=${{ github.sha }}
            BUILD_VERSION=${{ steps.ver.outputs.version }}
            SOURCE_DATE_EPOCH=${{ steps.ts.outputs.TIMESTAMP }}
          labels: ${{ steps.meta.outputs.labels }}
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: |
            type=gha,scope=linux/amd64
            type=gha,scope=linux/arm64
          cache-to: type=gha,mode=max
          provenance: false


================================================
FILE: .github/workflows/rebuild-issue-index.yml
================================================
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Rebuild Issue Index

on:
  schedule:
    - cron: "0 3 * * *"
  workflow_dispatch:

permissions: {}

env:
  EMBEDDING_MODEL: ${{ vars.EMBEDDING_MODEL }}

jobs:
  build-index:
    runs-on: ubuntu-24.04
    permissions:
      issues: read
      actions: write
      contents: read

    steps:
      - name: Checkout repository
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1

      - name: Pnpm Setup
        uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0

      - name: Set up Node.js
        uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
        with:
          node-version-file: 'package.json'

      - name: Get pnpm store directory
        shell: bash
        run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

      - name: Setup pnpm cache
        uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
        with:
          path: ${{ env.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - name: Install dependencies
        working-directory: bin/duplicate-detector
        env:
          CI: true
        run: pnpm install --frozen-lockfile

      - name: Build issue index
        working-directory: bin/duplicate-detector
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_REPOSITORY: ${{ github.repository }}
          INDEX_PATH: issue_index.json
        run: node build-index.mjs

      - name: Upload index artifact
        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
        with:
          name: issue-index
          path: bin/duplicate-detector/issue_index.json
          retention-days: 7


================================================
FILE: .github/workflows/release.yml
================================================
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Seerr Release

on:
  push:
    tags:
      - 'v*'

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  DOCKER_HUB: seerr/seerr

jobs:
  changelog:
    name: Generate changelog
    runs-on: ubuntu-24.04
    permissions:
      contents: read
    outputs:
      release_body: ${{ steps.git-cliff.outputs.content }}
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          fetch-depth: 0
          persist-credentials: false

      - name: Generate changelog
        id: git-cliff
        uses: orhun/git-cliff-action@c93ef52f3d0ddcdcc9bd5447d98d458a11cd4f72 # v4.7.1
        with:
          config: .github/cliff.toml
          args: -vv --current
        env:
          OUTPUT: CHANGELOG.md
          GITHUB_REPO: ${{ github.repository }}

  create-draft-release:
    name: Create draft release
    runs-on: ubuntu-24.04
    permissions:
      contents: write
    needs: changelog
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          persist-credentials: false

      - name: Draft Release
        run: gh release create ${GITHUB_REF_NAME} -t "Release ${GITHUB_REF_NAME}" -n "${RELEASE_BODY}" --draft
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          RELEASE_BODY: ${{ needs.changelog.outputs.release_body }}

  build:
    name: Build (${{ matrix.arch }})
    strategy:
      matrix:
        include:
          - runner: ubuntu-24.04
            platform: linux/amd64
            arch: amd64
          - runner: ubuntu-24.04-arm
            platform: linux/arm64
            arch: arm64
    runs-on: ${{ matrix.runner }}
    env:
      VERSION: ${{ github.ref_name }}
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          persist-credentials: false

      - name: Commit timestamp
        id: ts
        run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> "$GITHUB_OUTPUT"

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0

      - name: Warm cache [${{ matrix.platform }}]
        uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
        with:
          context: .
          file: ./Dockerfile
          platforms: ${{ matrix.platform }}
          push: false
          build-args: |
            COMMIT_TAG=${{ github.sha }}
            BUILD_VERSION=${{ env.VERSION }}
            SOURCE_DATE_EPOCH=${{ steps.ts.outputs.TIMESTAMP }}
          cache-from: type=gha,scope=${{ matrix.platform }}
          cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
          provenance: false

  publish:
    name: Publish multi-arch manifests
    needs: build
    runs-on: ubuntu-24.04
    permissions:
      contents: read
      packages: write
    outputs:
      image_digest: ${{ steps.digests.outputs.IMAGE_DIGEST }}
    env:
      VERSION: ${{ github.ref_name }}
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          persist-credentials: false

      - name: Commit timestamp
        id: ts
        run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> "$GITHUB_OUTPUT"

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0

      - name: Log in to Docker Hub
        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_TOKEN }}

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
        with:
          images: |
            ${{ env.DOCKER_HUB }}
            ghcr.io/${{ github.repository }}
          tags: |
            type=raw,value=${{ env.VERSION }}
          labels: |
            org.opencontainers.image.created=${{ steps.ts.outputs.TIMESTAMP }}

      - name: Build & Push (multi-arch)
        uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
        with:
          context: .
          file: ./Dockerfile
          platforms: linux/amd64,linux/arm64
          push: true
          build-args: |
            COMMIT_TAG=${{ github.sha }}
            BUILD_VERSION=${{ env.VERSION }}
            SOURCE_DATE_EPOCH=${{ steps.ts.outputs.TIMESTAMP }}
          labels: ${{ steps.meta.outputs.labels }}
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: |
            type=gha,scope=linux/amd64
            type=gha,scope=linux/arm64
          cache-to: type=gha,mode=max
          provenance: false

      - name: Resolve manifest digest
        id: digests
        run: |
          DIGEST=$(docker buildx imagetools inspect "${{ env.DOCKER_HUB }}:${{ env.VERSION }}" --format '{{json .Manifest.Digest}}' | tr -d '"')
          echo "IMAGE_DIGEST=$DIGEST" >> $GITHUB_OUTPUT

      - name: Also tag :latest (non-pre-release only)
        shell: bash
        if: ${{ !contains(env.VERSION, '-') }}
        run: |
          docker buildx imagetools create \
            -t ${{ env.DOCKER_HUB }}:latest \
            ${{ env.DOCKER_HUB }}:${{ env.VERSION }}

          docker buildx imagetools create \
            -t ghcr.io/${{ github.repository }}:latest \
            ghcr.io/${{ github.repository }}:${{ env.VERSION }}

  sign:
    name: Sign images and create SBOM attestations
    needs: publish
    runs-on: ubuntu-24.04
    permissions:
      contents: read
      id-token: write
      packages: write
    env:
      VERSION: ${{ github.ref_name }}
      COSIGN_YES: 'true'
    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          persist-credentials: false

      - name: Install Cosign
        uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0

      - name: Install Trivy
        uses: aquasecurity/setup-trivy@3fb12ec12f41e471780db15c232d5dd185dcb514 # v0.2.5

      - name: Log in to Docker Hub
        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_TOKEN }}

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Sign images
        run: |
          cosign sign --recursive "ghcr.io/${{ github.repository }}@${{ needs.publish.outputs.image_digest }}"
          cosign sign --recursive "${{ env.DOCKER_HUB }}@${{ needs.publish.outputs.image_digest }}"

      - name: Generate SBOMs
        run: |
          trivy image --format cyclonedx --output seerr-ghcr-image-${{ env.VERSION }}.sbom \
            "ghcr.io/${{ github.repository }}@${{ needs.publish.outputs.image_digest }}"

          trivy image --format cyclonedx --output seerr-dockerhub-image-${{ env.VERSION }}.sbom \
            "${{ env.DOCKER_HUB }}@${{ needs.publish.outputs.image_digest }}"

      - name: Attest SBOMs
        run: |
          cosign attest \
            --type cyclonedx \
            --predicate seerr-ghcr-image-${{ env.VERSION }}.sbom \
            "ghcr.io/${{ github.repository }}@${{ needs.publish.outputs.image_digest }}"

          cosign attest \
            --type cyclonedx \
            --predicate seerr-dockerhub-image-${{ env.VERSION }}.sbom \
            "${{ env.DOCKER_HUB }}@${{ needs.publish.outputs.image_digest }}"

      - name: Upload SBOMs
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: sboms-${{ env.VERSION }}
          path: '*.sbom'
          if-no-files-found: error
          retention-days: 1

  verify:
    name: Verify signatures and attestations
    needs: [publish, sign]
    runs-on: ubuntu-24.04
    permissions:
      contents: read
    env:
      VERSION: ${{ github.ref_name }}
    steps:
      - name: Install Cosign
        uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0

      - name: Verify signatures
        run: |
          cosign verify "ghcr.io/${{ github.repository }}@${{ needs.publish.outputs.image_digest }}" \
            --certificate-identity "https://github.com/${{ github.workflow_ref }}" \
            --certificate-oidc-issuer "https://token.actions.githubusercontent.com"

          cosign verify "${{ env.DOCKER_HUB }}@${{ needs.publish.outputs.image_digest }}" \
            --certificate-identity "https://github.com/${{ github.workflow_ref }}" \
            --certificate-oidc-issuer "https://token.actions.githubusercontent.com"

      # - name: Verify attestations
      #   run: |
      #     cosign verify-attestation "ghcr.io/${{ github.repository }}@${{ needs.publish.outputs.image_digest }}" \
      #       --type cyclonedx \
      #       --certificate-identity "https://github.com/${{ github.workflow_ref }}" \
      #       --certificate-oidc-issuer "https://token.actions.githubusercontent.com" > /dev/null

      #     cosign verify-attestation "${{ env.DOCKER_HUB }}@${{ needs.publish.outputs.image_digest }}" \
      #       --type cyclonedx \
      #       --certificate-identity "https://github.com/${{ github.workflow_ref }}" \
      #       --certificate-oidc-issuer "https://token.actions.githubusercontent.com" > /dev/null

  publish-release:
    name: Publish release
    needs: [create-draft-release, verify]
    runs-on: ubuntu-24.04
    permissions:
      contents: write
    env:
      VERSION: ${{ github.ref_name }}
    steps:
      - name: Publish release
        run: gh release edit "${{ env.VERSION }}" --draft=false --repo "${{ github.repository }}"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/renovate-helm-custom-hooks.yml
================================================
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Renovate Helm Hooks

on:
  pull_request:
    branches:
      - develop
    paths:
      - 'charts/**'

permissions: {}

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  renovate-post-run:
    name: Renovate Bump Chart Version
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    if: github.actor == 'renovate[bot]'
    steps:
      - name: Checkout code
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          fetch-depth: 0
          persist-credentials: false

      - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
        id: app-token
        with:
          app-id: 2138788
          private-key: ${{ secrets.APP_SEERR_HELM_PRIVATE_KEY }}

      - name: Set up chart-testing
        uses: helm/chart-testing-action@6ec842c01de15ebb84c8627d2744a0c2f2755c9f # v2.8.0

      - name: Run chart-testing (list-changed)
        id: list-changed
        run: |
          changed="$(ct list-changed --target-branch ${TARGET_BRANCH})"
          if [[ -n "$changed" ]]; then
            echo "changed=true" >> "$GITHUB_OUTPUT"
            echo "changed_list=${changed//$'\n'/ }" >> "$GITHUB_OUTPUT"
          fi
        env:
          TARGET_BRANCH: ${{ github.event.repository.default_branch }}

      - name: Bump chart version
        if: steps.list-changed.outputs.changed == 'true'
        env:
          CHART: ${{ steps.list-changed.outputs.changed_list }}
        run: |
          if [[ ! -d "${CHART}" ]]; then
            echo "${CHART} directory not found"
            exit 0
          fi

          # Extract current appVersion and chart version from Chart.yaml
          APP_VERSION=$(grep -e "^appVersion:" "$CHART/Chart.yaml" | cut -d ":" -f 2 | tr -d '[:space:]' | tr -d '"')
          CHART_VERSION=$(grep -e "^version:" "$CHART/Chart.yaml" | cut -d ":" -f 2 | tr -d '[:space:]' | tr -d '"')

          # Extract major, minor and patch versions of appVersion
          APP_MAJOR_VERSION=$(printf '%s' "$APP_VERSION" | cut -d "." -f 1)
          APP_MINOR_VERSION=$(printf '%s' "$APP_VERSION" | cut -d "." -f 2)
          APP_PATCH_VERSION=$(printf '%s' "$APP_VERSION" | cut -d "." -f 3)

          # Extract major, minor and patch versions of chart version
          CHART_MAJOR_VERSION=$(printf '%s' "$CHART_VERSION" | cut -d "." -f 1)
          CHART_MINOR_VERSION=$(printf '%s' "$CHART_VERSION" | cut -d "." -f 2)
          CHART_PATCH_VERSION=$(printf '%s' "$CHART_VERSION" | cut -d "." -f 3)

          # Get previous appVersion from the base commit of the pull request
          BASE_COMMIT=$(git merge-base origin/main HEAD)
          PREV_APP_VERSION=$(git show "$BASE_COMMIT":"$CHART/Chart.yaml" | grep -e "^appVersion:" | cut -d ":" -f 2 | tr -d '[:space:]' | tr -d '"')

          # Extract major, minor and patch versions of previous appVersion
          PREV_APP_MAJOR_VERSION=$(printf '%s' "$PREV_APP_VERSION" | cut -d "." -f 1)
          PREV_APP_MINOR_VERSION=$(printf '%s' "$PREV_APP_VERSION" | cut -d "." -f 2)
          PREV_APP_PATCH_VERSION=$(printf '%s' "$PREV_APP_VERSION" | cut -d "." -f 3)

          # Check if the major, minor, or patch version of appVersion has changed
          if [[ "$APP_MAJOR_VERSION" != "$PREV_APP_MAJOR_VERSION" ]]; then
            # Bump major version of the chart and reset minor and patch versions to 0
            CHART_MAJOR_VERSION=$((CHART_MAJOR_VERSION+1))
            CHART_MINOR_VERSION=0
            CHART_PATCH_VERSION=0
          elif [[ "$APP_MINOR_VERSION" != "$PREV_APP_MINOR_VERSION" ]]; then
            # Bump minor version of the chart and reset patch version to 0
            CHART_MINOR_VERSION=$((CHART_MINOR_VERSION+1))
            CHART_PATCH_VERSION=0
          elif [[ "$APP_PATCH_VERSION" != "$PREV_APP_PATCH_VERSION" ]]; then
            # Bump patch version of the chart
            CHART_PATCH_VERSION=$((CHART_PATCH_VERSION+1))
          fi

          # Update the chart version in Chart.yaml
          CHART_NEW_VERSION="${CHART_MAJOR_VERSION}.${CHART_MINOR_VERSION}.${CHART_PATCH_VERSION}"
          sed -i "s/^version:.*/version: ${CHART_NEW_VERSION}/" "$CHART/Chart.yaml"

      - name: Ensure documentation is updated
        if: steps.list-changed.outputs.changed == 'true'
        uses: docker://jnorwood/helm-docs:v1.14.2@sha256:7e562b49ab6b1dbc50c3da8f2dd6ffa8a5c6bba327b1c6335cc15ce29267979c

      - name: Commit changes
        if: steps.list-changed.outputs.changed == 'true'
        env:
          CHART: ${{ steps.list-changed.outputs.changed_list }}
          GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
          GITHUB_HEAD_REF: ${{ github.head_ref }}
        run: |
          # Define the target directory
          TARGET_DIR="$CHART"

          # Fetch deleted files in the target directory
          DELETED_FILES=$(git diff --diff-filter=D --name-only HEAD -- "$TARGET_DIR")

          # Fetch added/modified files in the target directory
          MODIFIED_FILES=$(git diff --diff-filter=ACM --name-only HEAD -- "$TARGET_DIR")

          # Create a temporary file for JSON output
          FILE_CHANGES_JSON_FILE=$(mktemp)

          # Initialize JSON structure in the file
          echo '{ "deletions": [], "additions": [] }' > "$FILE_CHANGES_JSON_FILE"

          # Add deletions
          for file in $DELETED_FILES; do
            jq --arg path "$file" '.deletions += [{"path": $path}]' "$FILE_CHANGES_JSON_FILE" > "$FILE_CHANGES_JSON_FILE.tmp"
            mv "$FILE_CHANGES_JSON_FILE.tmp" "$FILE_CHANGES_JSON_FILE"
          done

          # Add additions (new or modified files)
          for file in $MODIFIED_FILES; do
            BASE64_CONTENT=$(base64 -w 0 <"$file")  # Encode file content
            jq --arg path "$file" --arg content "$BASE64_CONTENT" \
              '.additions += [{"path": $path, "contents": $content}]' "$FILE_CHANGES_JSON_FILE" > "$FILE_CHANGES_JSON_FILE.tmp"
            mv "$FILE_CHANGES_JSON_FILE.tmp" "$FILE_CHANGES_JSON_FILE"
          done

          # Create a temporary file for the final JSON payload
          JSON_PAYLOAD_FILE=$(mktemp)

          # Construct the final JSON using jq and store it in a file
          jq -n --arg repo "$GITHUB_REPOSITORY" \
                --arg branch "$GITHUB_HEAD_REF" \
                --arg message "fix: post upgrade changes from renovate" \
                --arg expectedOid "$GITHUB_SHA" \
                --slurpfile fileChanges "$FILE_CHANGES_JSON_FILE" \
                '{
                  query: "mutation ($input: CreateCommitOnBranchInput!) {
                    createCommitOnBranch(input: $input) {
                      commit {
                        url
                      }
                    }
                  }",
                  variables: {
                    input: {
                      branch: {
                        repositoryNameWithOwner: $repo,
                        branchName: $branch
                      },
                      message: { headline: $message },
                      fileChanges: $fileChanges[0],
                      expectedHeadOid: $expectedOid
                    }
                  }
                }' > "$JSON_PAYLOAD_FILE"

          # Call GitHub API
          curl https://api.github.com/graphql -f \
               -sSf -H "Authorization: Bearer $GITHUB_TOKEN" \
               --data "@$JSON_PAYLOAD_FILE"

          # Clean up temporary files
          rm "$FILE_CHANGES_JSON_FILE" "$JSON_PAYLOAD_FILE"


================================================
FILE: .github/workflows/seerr-labeller.yml
================================================
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: 'Seerr Labeller'

on:
  pull_request_target:
    types: [labeled, unlabeled, reopened]
  issues:
    types: [labeled, unlabeled, reopened]

permissions: {}

jobs:
  ai-generated-support:
    if: >
      github.event_name == 'pull_request_target' &&
      (github.event.label.name == 'ai-generated' || (github.event.action == 'reopened' && contains(github.event.pull_request.labels.*.name, 'ai-generated')))
    runs-on: ubuntu-24.04
    concurrency:
      group: ai-generated-${{ github.event.pull_request.number }}
      cancel-in-progress: true
    permissions:
      pull-requests: write
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GH_REPO: ${{ github.repository }}
      NUMBER: ${{ github.event.pull_request.number }}
      PR_AUTHOR: ${{ github.event.pull_request.user.login }}
    steps:
      - name: Label added, comment and close pull request
        if: github.event.action == 'labeled' && github.event.label.name == 'ai-generated'
        shell: bash
        env:
          BODY: >
            :wave: @${{ env.PR_AUTHOR }}, thank you for your contribution!

            However, this pull request has been closed because it appears to contain a significant amount of AI-generated code without sufficient human review or supervision.

            AI-generated code can often introduce subtle bugs, poor design patterns, or inconsistent styles that make long-term maintenance difficult and reduce overall code quality. For the sake of the project's future stability and readability, we require that all contributions meet our established coding standards and demonstrate clear developer oversight.

            This pull request is also too large for effective human review. Please discuss with us on how to break down these changes into smaller, more focused PRs to ensure a thorough and efficient review process.
            If you'd like to revise and resubmit your changes with careful review and cleanup, we'd be happy to take another look.
        run: |
          retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; }
          retry gh pr comment "$NUMBER" -R "$GH_REPO" -b "$BODY" || true
          retry gh pr close "$NUMBER" -R "$GH_REPO" || true
          gh pr lock "$NUMBER" -R "$GH_REPO" -r "spam" || true

      - name: Label removed, reopen and unlock pull request
        if: github.event.action == 'unlabeled' && github.event.label.name == 'ai-generated'
        shell: bash
        run: |
          retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; }
          retry gh pr reopen "$NUMBER" -R "$GH_REPO" || true
          gh pr unlock "$NUMBER" -R "$GH_REPO" || true

      - name: Remove AI-generated label on manual reopen
        if: github.event.action == 'reopened'
        shell: bash
        run: |
          gh pr edit "$NUMBER" -R "$GH_REPO" --remove-label "ai-generated" || true
          gh pr unlock "$NUMBER" -R "$GH_REPO" || true

  support:
    if: >
      github.event_name == 'issues' &&
      (github.event.label.name == 'support' ||
      (github.event.action == 'reopened' && contains(github.event.issue.labels.*.name, 'support')))
    runs-on: ubuntu-24.04
    concurrency:
      group: support-${{ github.event.issue.number }}
      cancel-in-progress: true
    permissions:
      issues: write
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GH_REPO: ${{ github.repository }}
      NUMBER: ${{ github.event.issue.number }}
      ISSUE_AUTHOR: ${{ github.event.issue.user.login }}
    steps:
      - name: Label added, comment and close issue
        if: github.event.action == 'labeled' && github.event.label.name == 'support'
        shell: bash
        env:
          BODY: >
            :wave: @${{ env.ISSUE_AUTHOR }}, we use the issue tracker exclusively
            for bug reports and feature requests. However, this issue appears
            to be a support request. Please use our support channels
            to get help with Seerr.

            - [Discord](https://discord.gg/seerr)
        run: |
          retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; }
          retry gh issue comment "$NUMBER" -R "$GH_REPO" -b "$BODY" || true
          retry gh issue close "$NUMBER" -R "$GH_REPO" || true
          gh issue lock "$NUMBER" -R "$GH_REPO" -r "off_topic" || true

      - name: Label removed, reopen and unlock issue
        if: github.event.action == 'unlabeled' && github.event.label.name == 'support'
        shell: bash
        run: |
          retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; }
          retry gh issue reopen "$NUMBER" -R "$GH_REPO" || true
          gh issue unlock "$NUMBER" -R "$GH_REPO" || true

      - name: Remove support label on manual reopen
        if: github.event.action == 'reopened'
        shell: bash
        run: |
          gh issue edit "$NUMBER" -R "$GH_REPO" --remove-label "support" || true
          gh issue unlock "$NUMBER" -R "$GH_REPO" || true


================================================
FILE: .github/workflows/semantic-pr.yml
================================================
name: "Semantic PR"

on:
  pull_request_target:
    types:
      - opened
      - reopened
      - edited
      - synchronize

permissions: {}

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  main:
    name: Validate PR Title
    runs-on: ubuntu-slim
    permissions:
      contents: read
      pull-requests: read
      checks: write
    steps:
      - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/stale.yml
================================================
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Close Stale Issues and PRs

on:
  schedule:
    - cron: '0 7 * * *'

permissions: {}

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  stale:
    name: Close stale issues and PRs
    runs-on: ubuntu-24.04
    permissions:
      actions: write
      issues: write
      pull-requests: write
    steps:
      - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
        with:
          any-of-labels: "pending author's response"
          exempt-issue-labels: 'confirmed'
          days-before-stale: 30
          days-before-close: 30
          stale-issue-label: 'stale'
          stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Please provide an update or the requested information to keep it open.'
          close-issue-message: 'This issue was closed because it has been stalled for 30 days with no activity. Feel free to reopen it once you provide the required update or information.'
          stale-pr-label: 'stale'
          stale-pr-message: 'This PR is stale because it has been open 30 days with no activity. Please address the feedback or provide an update to keep it open.'
          close-pr-message: 'This PR was closed because it has been stalled for 30 days with no activity. You can reopen it once you address the feedback or provide the requested changes.'


================================================
FILE: .github/workflows/test-docs-deploy.yml
================================================
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Test Docs deployment

on:
  pull_request:
    branches:
      - develop
    paths:
      - 'docs/**'
      - 'gen-docs/**'

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  test-deploy:
    name: Test deployment
    runs-on: ubuntu-24.04
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          fetch-depth: 0
          persist-credentials: false

      - name: Set up Node.js
        uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
        with:
          node-version-file: package.json
          package-manager-cache: false

      - name: Pnpm Setup
        uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0

      - name: Get pnpm store directory
        shell: sh
        run: |
          echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

      - name: Setup pnpm cache
        uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
        with:
          path: ${{ env.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - name: Install dependencies
        run: |
          cd gen-docs
          pnpm install --frozen-lockfile

      - name: Build website
        run: |
          cd gen-docs
          pnpm build


================================================
FILE: .github/workflows/trivy-scan.yml
================================================
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Trivy Container Vulnerability Scan

on:
  workflow_run:
    workflows:
      - Seerr Release
    types:
      - completed
  schedule:
    - cron: '50 7 * * 5'
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  trivy:
    if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
    name: Scan latest container image
    runs-on: ubuntu-24.04

    permissions:
      contents: read
      security-events: write

    env:
      TRIVY_CACHE_DIR: .trivycache

    steps:
      - name: Checkout
        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
        with:
          fetch-depth: 0
          persist-credentials: false

      - name: Cache Trivy DB
        uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
        with:
          path: .trivycache
          key: trivy-${{ runner.os }}-${{ hashFiles('**/Dockerfile') }}
          restore-keys: |
            trivy-${{ runner.os }}-

      - name: Run Trivy image scan
        uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
        with:
          image-ref: ghcr.io/${{ github.repository }}:latest
          format: sarif
          output: trivy.sarif
          ignore-unfixed: true

      - name: Upload SARIF to code scanning
        uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
        with:
          sarif_file: trivy.sarif


================================================
FILE: .gitignore
================================================
# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage
lcov.info

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel

# database
config/db/*.sqlite3*
config/settings.json
config/settings.old.json

# logs
config/logs/*.log*
config/logs/*.json
config/logs/*.log.gz
config/logs/*.json.gz
config/logs/*-audit.json

# anidb mapping file
config/anime-list.xml

# dist files
dist

# sqlite journal
config/db/db.sqlite3-journal

# VS Code
.vscode/launch.json

# Cypress
cypress.env.json
cypress/videos
cypress/screenshots

# ESLint
.eslintcache

# TS Build Info
tsconfig.tsbuildinfo

# Webstorm
.idea

# Config Cache Directory
config/cache

# Docker compose
compose.override.yaml


================================================
FILE: .husky/commit-msg
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

[ -n "$HUSKY_BYPASS" ] || npx commitlint --edit $1


================================================
FILE: .husky/pre-commit
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged


================================================
FILE: .husky/prepare-commit-msg
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

exec < /dev/tty && npx cz --hook || true


================================================
FILE: .npmrc
================================================
engine-strict=true


================================================
FILE: .prettierignore
================================================
# Generated files which we would not like to format
.next/
dist/
config/
cache/config.json
pnpm-lock.yaml
cypress/config/settings.cypress.json
.github
.vscode

# assets
src/assets/
docs/
public/*
!public/sw.js

# helm charts
**/charts

# Prettier breaks GitHub alert syntax in markdown
*.md


================================================
FILE: .prettierrc.js
================================================
module.exports = {
  plugins: ['prettier-plugin-organize-imports', 'prettier-plugin-tailwindcss'],
  singleQuote: true,
  trailingComma: 'es5',
  overrides: [
    {
      files: 'pnpm-lock.yaml',
      options: {
        rangeEnd: 0, // default: Infinity
      },
    },
    {
      files: 'gen-docs/pnpm-lock.yaml',
      options: {
        rangeEnd: 0, // default: Infinity
      },
    },
    {
      files: 'charts/**',
      options: {
        rangeEnd: 0, // default: Infinity
      },
    },
    {
      files: 'cypress/config/settings.cypress.json',
      options: {
        rangeEnd: 0,
      },
    },
    {
      files: 'public/offline.html',
      options: {
        rangeEnd: 0,
      },
    },
    {
      files: 'cache/config.json',
      options: {
        rangeEnd: 0, // default: Infinity
      },
    },
  ],
};


================================================
FILE: .vscode/extensions.json
================================================
{
  // see
  //  - https://code.visualstudio.com/docs/editor/extension-gallery#_workspace-recommended-extensions
  "recommendations": [
    // https://marketplace.visualstudio.com/items?itemName=EditorConfig.editorconfig
    "EditorConfig.editorconfig",

    // https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint
    "dbaeumer.vscode-eslint",

    // https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode
    "esbenp.prettier-vscode",

    // https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest
    "Orta.vscode-jest",

    "stylelint.vscode-stylelint",

    "bradlc.vscode-tailwindcss",
    "firsttris.vscode-jest-runner"
  ]
}


================================================
FILE: .vscode/settings.json
================================================
{
  "eslint.enable": true,
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact"
  ],
  "typescript.tsdk": "node_modules/typescript/lib",
  "sqltools.connections": [
    {
      "previewLimit": 50,
      "driver": "SQLite",
      "name": "Local SQLite",
      "database": "./config/db/db.sqlite3"
    }
  ],
  "editor.formatOnSave": true,
  "typescript.preferences.importModuleSpecifier": "non-relative",
  "files.associations": {
    "globals.css": "tailwindcss"
  },
  "i18n-ally.localesPaths": [
    "src/i18n/locale"
  ],
  "yaml.format.singleQuote": true,
  "jestrunner.enableTestExplorer": true,
  "jestrunner.defaultTestPatterns": [
    "server/**/*.{test,spec}.?(c|m)[jt]s?(x)",
  ],
  "jestrunner.nodeTestCommand": "pnpm test",
  "jestrunner.changeDirectoryToWorkspaceRoot": true,
  "jestrunner.projectPath": "."
}


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
  overall community

Examples of unacceptable behavior include:

- The use of sexualized language or imagery, and sexual attention or
  advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
  address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[ryan@sct.dev](mailto:ryan@sct.dev).
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series
of actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within
the community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].

Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][mozilla coc].

For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][faq]. Translations are available
at [https://www.contributor-covenant.org/translations][translations].

[homepage]: https://www.contributor-covenant.org
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
[mozilla coc]: https://github.com/mozilla/diversity
[faq]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Seerr

All help is welcome and greatly appreciated! If you would like to contribute to the project, the following instructions should get you started...

## AI Assistance Notice

> [!IMPORTANT]
>
> Automated AI-generated contributions without human review are not allowed and will be rejected.
> This is an open-source project maintained by volunteers.
> We do not have the resources to review pull requests that could have been avoided with proper human oversight.
> While we have no issue with contributors using AI tools as an aid, it is your responsibility as a contributor to ensure that all submissions are carefully reviewed and meet our quality standards.
> Submissions that appear to be unreviewed AI output will be considered low-effort and may result in a ban.
>
> If you are using **any kind of AI assistance** to contribute to Seerr,
> it must be disclosed in the pull request.

If you are using any kind of AI assistance while contributing to Seerr,
**this must be disclosed in the pull request**, along with the extent to
which AI assistance was used (e.g. docs only vs. code generation).
If PR responses are being generated by an AI, disclose that as well.
As a small exception, trivial tab-completion doesn't need to be disclosed,
so long as it is limited to single keywords or short phrases.

An example disclosure:

> This PR was written primarily by Claude Code.

Or a more detailed disclosure:

> I consulted ChatGPT to understand the codebase but the solution
> was fully authored manually by myself.

Failure to disclose this is first and foremost rude to the human operators
on the other end of the pull request, but it also makes it difficult to
determine how much scrutiny to apply to the contribution.

In a perfect world, AI assistance would produce equal or higher quality
work than any human. That isn't the world we live in today, and in most cases
it's generating slop. I say this despite being a fan of and using them
successfully myself (with heavy supervision)!

When using AI assistance, we expect contributors to understand the code
that is produced and be able to answer critical questions about it. It
isn't a maintainers job to review a PR so broken that it requires
significant rework to be acceptable.

Please be respectful to maintainers and disclose AI assistance.

## Development

### Tools Required

- HTML/Typescript/Javascript editor
- [VSCode](https://code.visualstudio.com/) is recommended. Upon opening the project, a few extensions will be automatically recommended for install.
- [NodeJS](https://nodejs.org/en/download/) (Node 22.x)
- [Pnpm](https://pnpm.io/cli/install)
- [Git](https://git-scm.com/downloads)

### Getting Started

1. [Fork](https://help.github.com/articles/fork-a-repo/) the repository to your own GitHub account and [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device:

   ```bash
   git clone https://github.com/YOUR_USERNAME/seerr.git
   cd seerr/
   ```

2. Add the remote `upstream`:

   ```bash
   git remote add upstream https://github.com/seerr-team/seerr.git
   ```

3. Create a new branch:

   ```bash
   git switch -c BRANCH_NAME develop
   ```

   - It is recommended to give your branch a meaningful name, relevant to the feature or fix you are working on.
     - Good examples:
       - `docs-docker`
       - `feature-new-system`
       - `fix-title-cards`
     - Bad examples:
       - `bug`
       - `docs`
       - `feature`
       - `fix`
       - `patch`

4. Run the development environment:

   ```bash
   pnpm install
   pnpm dev
   ```

   - Alternatively, you can use [Docker](https://www.docker.com/) with `docker compose up -d`. This method does not require installing NodeJS or Yarn on your machine directly.

5. Create your patch and test your changes.

   - Be sure to follow both the [code](#contributing-code) and [UI text](#ui-text-style) guidelines.
   - Should you need to update your fork, you can do so by rebasing from `upstream`:

     ```bash
     git fetch upstream
     git rebase upstream/develop
     git push origin BRANCH_NAME -f
     ```

### Helm Chart

Tools Required:

- [Helm](https://helm.sh/docs/intro/install/)
- [helm-docs](https://github.com/norwoodj/helm-docs)

Steps:

1. Make the necessary changes.
2. Test your changes.
3. Update the `version` in `charts/seerr-chart/Chart.yaml` following [Semantic Versioning (SemVer)](https://semver.org/).
4. Run the `helm-docs` command to regenerate the chart's README.

### Contributing Code

- If you are taking on an existing bug or feature ticket, please comment on the [issue](/../../issues) to avoid multiple people working on the same thing.
- Pull requests with titles not following [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) will **not** be merged. PR titles are automatically checked for compliance.
- Please make meaningful commits, or squash them prior to opening a pull request.
  - Do not squash commits once people have begun reviewing your changes.
- Always rebase your branch to the latest `develop` branch.
- It is your responsibility to keep your branch up-to-date. Your work will **not** be merged unless it is rebased off the latest `develop` branch.
- You can create a "draft" pull request early to get feedback on your work.
- Your code **must** be formatted correctly, or the tests will fail.
  - We use Prettier to format our code base. It should automatically run with a Git hook, but it is recommended to have the Prettier extension installed in your editor and format on save.
- If you have questions or need help, you can reach out via [Discussions](/../../discussions) or our [Discord server](https://discord.gg/seerr).
- Only open pull requests to `develop`, never `master`! Any pull requests opened to `master` will be closed.

### UI Text Style

When adding new UI text, please try to adhere to the following guidelines:

1. Be concise and clear, and use as few words as possible to make your point.
2. Use the Oxford comma where appropriate.
3. Use the appropriate Unicode characters for ellipses, arrows, and other special characters/symbols.
4. Capitalize proper nouns, such as Plex, Radarr, Sonarr, Telegram, Slack, Pushover, etc. Be sure to also use the official capitalization for any abbreviations; e.g., IMDb has a lowercase 'b', whereas TMDB and TheTVDB have a capital 'B'.
5. Title case headings, button text, and form labels. Note that verbs such as "is" should be capitalized, whereas prepositions like "from" should be lowercase (unless as the first or last word of the string, in which case they are also capitalized).
6. Capitalize the first word in validation error messages, dropdowns, and form "tips." These strings should not end in punctuation.
7. Ensure that toast notification strings are complete sentences ending in punctuation.
8. If an additional description or "tip" is required for a form field, it should be styled using the global CSS class `label-tip`.
9. In full sentences, abbreviations like "info" or "auto" should not be used in place of full words, unless referencing the name/label of a specific setting or option which has an abbreviation in its name.
10. Do your best to check for spelling errors and grammatical mistakes.
11. Do not misspell "Seerr."

## Translation

We use [Weblate](https://translate.seerr.dev/projects/seerr/seerr-frontend/) for our translations, and your help with localizing Seerr would be greatly appreciated! If your language is not listed below, please [open a feature request](/../../issues/new/choose).

<a href="https://translate.seerr.dev/engage/seerr/"><img src="https://translate.seerr.dev/widget/seerr/multi-auto.svg" alt="Translation status" /></a>

## Migrations

If you are adding a new feature that requires a database migration, you will need to create 2 migrations: one for SQLite and one for PostgreSQL. Here is how you could do it:

1. Create a PostgreSQL database or use an existing one:

```bash
sudo docker run --name postgres-seerr -e POSTGRES_PASSWORD=postgres -d -p 127.0.0.1:5432:5432/tcp postgres:latest
```

2. Reset the SQLite database and the PostgreSQL database:

```bash
rm config/db/db.*
rm config/settings.*
PGPASSWORD=postgres sudo docker exec -it postgres-seerr /usr/bin/psql -h 127.0.0.1 -U postgres -c "DROP DATABASE IF EXISTS seerr;"
PGPASSWORD=postgres sudo docker exec -it postgres-seerr /usr/bin/psql -h 127.0.0.1 -U postgres -c "CREATE DATABASE seerr;"
```

3. Switch to the `develop` branch and create the original database for SQLite and PostgreSQL so that TypeORM can automatically generate the migrations:

```bash
git switch develop
pnpm i
rm -r .next dist; pnpm build
pnpm start
DB_TYPE="postgres" DB_USER=postgres DB_PASS=postgres pnpm start
```

(You can shutdown the server once the message "Server ready on 5055" appears)

4. Let TypeORM generate the migrations:

```bash
git switch -c your-feature-branch
pnpm i
pnpm migration:generate server/migration/sqlite/YourMigrationName
DB_TYPE="postgres" DB_USER=postgres DB_PASS=postgres pnpm migration:generate server/migration/postgres/YourMigrationName
```

## Attribution

This contribution guide was inspired by the [Next.js](https://github.com/vercel/next.js), [Radarr](https://github.com/Radarr/Radarr), and [Ghostty](https://github.com/ghostty-org/ghostty) contribution guides.


================================================
FILE: Dockerfile
================================================
FROM node:22.22.0-alpine3.22@sha256:7aa86fa052f6e4b101557ccb56717cb4311be1334381f526fe013418fe157384 AS base
ARG SOURCE_DATE_EPOCH
ARG TARGETPLATFORM
ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64}

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

COPY . ./app
WORKDIR /app

FROM base AS prod-deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store CI=true pnpm install --prod --frozen-lockfile

FROM base AS build

ARG COMMIT_TAG
ENV COMMIT_TAG=${COMMIT_TAG}

RUN \
  case "${TARGETPLATFORM}" in \
  'linux/arm64' | 'linux/arm/v7') \
  apk update && \
  apk add --no-cache python3 make g++ gcc libc6-compat bash && \
  npm install --global node-gyp \
  ;; \
  esac

RUN --mount=type=cache,id=pnpm,target=/pnpm/store CYPRESS_INSTALL_BINARY=0 pnpm install --frozen-lockfile

RUN pnpm build

RUN rm -rf .next/cache

FROM node:22.22.0-alpine3.22@sha256:7aa86fa052f6e4b101557ccb56717cb4311be1334381f526fe013418fe157384
ARG SOURCE_DATE_EPOCH
ARG COMMIT_TAG
ENV NODE_ENV=production
ENV COMMIT_TAG=${COMMIT_TAG}

RUN apk add --no-cache tzdata

USER node:node

WORKDIR /app

COPY --chown=node:node . .
COPY --chown=node:node --from=prod-deps /app/node_modules ./node_modules
COPY --chown=node:node --from=build /app/.next ./.next
COPY --chown=node:node --from=build /app/dist ./dist

RUN touch config/DOCKER && \
  echo "{\"commitTag\": \"${COMMIT_TAG}\"}" > committag.json

EXPOSE 5055

CMD [ "npm", "start" ]


================================================
FILE: Dockerfile.local
================================================
FROM node:22.22.0-alpine3.22@sha256:7aa86fa052f6e4b101557ccb56717cb4311be1334381f526fe013418fe157384

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

COPY . /app
WORKDIR /app

RUN pnpm install

CMD pnpm dev


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020 sct

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<p align="center">
<img src="./public/logo_full.svg" alt="Seerr" style="margin: 20px 0;">
</p>
<p align="center">
<img src="https://github.com/seerr-team/seerr/actions/workflows/release.yml/badge.svg" alt="Seerr Release" />
<img src="https://github.com/seerr-team/seerr/actions/workflows/ci.yml/badge.svg" alt="Seerr CI">
</p>
<p align="center">
<a href="https://discord.gg/seerr"><img src="https://img.shields.io/discord/783137440809746482" alt="Discord"></a>
<a href="https://hub.docker.com/r/seerr/seerr"><img src="https://img.shields.io/docker/pulls/seerr/seerr" alt="Docker pulls"></a>
<a href="https://translate.seerr.dev/engage/seerr/"><img src="https://translate.seerr.dev/widget/seerr/svg-badge.svg" alt="Translation status" /></a>
<a href="https://github.com/seerr-team/seerr/blob/develop/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/seerr-team/seerr"></a>

**Seerr** is a free and open source software application for managing requests for your media library. It integrates with the media server of your choice: [Jellyfin](https://jellyfin.org), [Plex](https://plex.tv), and [Emby](https://emby.media/). In addition, it integrates with your existing services, such as **[Sonarr](https://sonarr.tv/)**, **[Radarr](https://radarr.video/)**.

## Current Features

- Full Jellyfin/Emby/Plex integration including authentication with user import & management.
- Support for **PostgreSQL** and **SQLite** databases.
- Supports Movies, Shows and Mixed Libraries.
- Ability to change email addresses for SMTP purposes.
- Easy integration with your existing services. Currently, Seerr supports Sonarr and Radarr. More to come!
- Jellyfin/Emby/Plex library scan, to keep track of the titles which are already available.
- Customizable request system, which allows users to request individual seasons or movies in a friendly, easy-to-use interface.
- Incredibly simple request management UI. Don't dig through the app to simply approve recent requests!
- Granular permission system.
- Support for various notification agents.
- Mobile-friendly design, for when you need to approve requests on the go!
- Support for watchlisting & blocklisting media.

With more features on the way! Check out our [issue tracker](/../../issues) to see the features which have already been requested.

## Getting Started

Check out our documentation for instructions on how to install and run Seerr:

https://docs.seerr.dev/getting-started/

## Preview

<img src="./public/preview.jpg" alt="Seerr application preview" />

## Migrating from Overseerr/Jellyseerr to Seerr

Read our [release announcement](https://docs.seerr.dev/blog/seerr-release) to learn what Seerr means for Jellyseerr and Overseerr users.

Please follow our [migration guide](https://docs.seerr.dev/migration-guide) for detailed instructions on migrating from Overseerr or Jellyseerr.

## Support

- Check out the [Seerr Documentation](https://docs.seerr.dev) before asking for help. Your question might already be in the docs!
- You can get support on [Discord](https://discord.gg/seerr).
- You can ask questions in the Help category of our [GitHub Discussions](/../../discussions).
- Bug reports and feature requests can be submitted via [GitHub Issues](/../../issues).

## API Documentation

You can access the API documentation from your local Seerr install at http://localhost:5055/api-docs

## Community

You can ask questions, share ideas, and more in [GitHub Discussions](/../../discussions).

If you would like to chat with other members of our growing community, [join the Seerr Discord server](https://discord.gg/seerr)!

Our [Code of Conduct](./CODE_OF_CONDUCT.md) applies to all Seerr community channels.

## Contributing

You can help improve Seerr too! Check out our [Contribution Guide](./CONTRIBUTING.md) to get started.

## Contributors ✨

[![Contributors](https://opencollective.com/seerr/contributors.svg?width=890)](https://opencollective.com/seerr/#backers)

[![Become a Backer](https://opencollective.com/seerr/backers.svg)](https://opencollective.com/seerr/#backers)
[![Become a Sponsor](https://opencollective.com/seerr/sponsors.svg)](https://opencollective.com/seerr/#sponsors)


================================================
FILE: SECURITY.md
================================================
# Security Policy

## Reporting Security Issues

Maintainers and community take security bugs seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.

To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](../../security/advisories/new) tab.

**Please do not report security vulnerabilities through public GitHub issues, discussions, or Discord.**

## AI Assistance Notice

> [!IMPORTANT]
>
> Automated AI-generated contributions without human review are not allowed and will be rejected.
> This is an open-source project maintained by volunteers.
> We do not have the resources to review pull requests that could have been avoided with proper human oversight.
> While we have no issue with contributors using AI tools as an aid, it is your responsibility as a contributor to ensure that all submissions are carefully reviewed and meet our quality standards.
> Submissions that appear to be unreviewed AI output will be considered low-effort and may result in a ban.
>
> If you are using **any kind of AI assistance** to contribute to Seerr,
> it must be disclosed in the pull request.

If you are using any kind of AI assistance while contributing to Seerr,
**this must be disclosed in the pull request**, along with the extent to
which AI assistance was used (e.g. docs only vs. code generation).
If security advisory responses are being generated by an AI, disclose that as well.
As a small exception, trivial tab-completion doesn't need to be disclosed,
so long as it is limited to single keywords or short phrases.

An example disclosure:

> This security advisory was written primarily by Claude Code.

Or a more detailed disclosure:

> I consulted ChatGPT to understand the codebase but the solution
> was fully authored manually by myself.

Failure to disclose this is first and foremost rude to the human operators
on the other end of the pull request, but it also makes it difficult to
determine how much scrutiny to apply to the contribution.

In a perfect world, AI assistance would produce equal or higher quality
work than any human. That isn't the world we live in today, and in most cases
it's generating slop. I say this despite being a fan of and using them
successfully myself (with heavy supervision)!

When using AI assistance, we expect contributors to understand the code
that is produced and be able to answer critical questions about it. It
isn't a maintainers job to review a PR so broken that it requires
significant rework to be acceptable.

Please be respectful to maintainers and disclose AI assistance.

## What to Include in Your Report

To help us better understand and resolve the issue, please include as much of the following information as possible:

- Full paths of source file(s) related to the manifestation of the issue
- The location of the affected source code (tag/branch/commit or direct URL)
- Any special configuration required to reproduce the issue
- Step-by-step instructions to reproduce the issue
- Proof-of-concept or exploit code (if possible)
- Impact of the issue

## Response Timeline

We will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.

## Disclosure Policy

- Security issues will be disclosed in a coordinated manner
- We will credit reporters in the security advisory unless anonymity is requested
- We request that you do not publicly disclose the issue until we have released a fix

## Third-Party Dependencies

If you discover a security vulnerability in a third-party dependency used by Seerr, please report it directly to the maintainers of that module. You can also notify us through our security advisory process so we can:

- Track the issue and monitor for updates
- Apply patches or workarounds if available
- Coordinate with upstream maintainers when necessary
- Communicate the impact to our users

We regularly monitor and update our dependencies to address known security vulnerabilities.

## Security Updates

Security updates and advisories will be published on our [GitHub Security Advisories page](../../security/advisories).

## Community

For general questions and support (non-security related):

- [GitHub Discussions](../../discussions)
- [Discord](https://discord.gg/seerr)


================================================
FILE: bin/check-i18n.js
================================================
#!/usr/bin/env node

/**
 * Check that i18n locale files are in sync with extracted messages.
 * Runs `pnpm i18n:extract` and compares en.json; exits 1 if they differ.
 */
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');

const localePath = path.join(
  __dirname,
  '..',
  'src',
  'i18n',
  'locale',
  'en.json'
);
const backupPath = `${localePath}.bak`;

try {
  fs.copyFileSync(localePath, backupPath);
  execSync('pnpm i18n:extract', { stdio: 'inherit' });
  const original = fs.readFileSync(backupPath, 'utf8');
  const extracted = fs.readFileSync(localePath, 'utf8');
  fs.unlinkSync(backupPath);

  if (original !== extracted) {
    console.error(
      "i18n messages are out of sync. Please run 'pnpm i18n:extract' and commit the changes."
    );
    process.exit(1);
  }
} catch (err) {
  if (fs.existsSync(backupPath)) {
    fs.unlinkSync(backupPath);
  }
  throw err;
}


================================================
FILE: bin/duplicate-detector/.gitignore
================================================
node_modules/


================================================
FILE: bin/duplicate-detector/build-index.mjs
================================================
#!/usr/bin/env node
/**
 * Build Issue Embedding Index
 *
 * Fetches all open issues and recently closed ones,
 * generates embeddings using a local ONNX transformer model,
 * and saves them as a JSON artifact for the duplicate detector.
 */

import { pipeline } from '@huggingface/transformers';
import { mkdirSync, writeFileSync } from 'node:fs';
import { dirname } from 'node:path';
import { fetchIssues, issueText } from './utils.mjs';

const MODEL_NAME = process.env.EMBEDDING_MODEL || 'Xenova/all-MiniLM-L6-v2';
const OUTPUT_PATH = 'issue_index.json';
const INCLUDE_CLOSED_DAYS = 90;
const MAX_ISSUES = 5000;
const BATCH_SIZE = 64;

async function main() {
  console.log('Fetching open issues...');
  const openIssues = await fetchIssues({
    state: 'open',
    maxIssues: MAX_ISSUES,
  });
  console.log(`Fetched ${openIssues.length} open issues`);

  const since = new Date(
    Date.now() - INCLUDE_CLOSED_DAYS * 24 * 60 * 60 * 1000
  ).toISOString();
  console.log(
    `Fetching closed issues from last ${INCLUDE_CLOSED_DAYS} days...`
  );

  const closedIssues = await fetchIssues({
    state: 'closed',
    since,
    maxIssues: MAX_ISSUES,
  });
  console.log(`Fetched ${closedIssues.length} closed issues`);
  let allIssues = [...openIssues, ...closedIssues];

  const seen = new Set();
  allIssues = allIssues.filter((issue) => {
    if (seen.has(issue.number)) return false;
    seen.add(issue.number);
    return true;
  });

  console.log(`Total unique issues to index: ${allIssues.length}`);

  if (allIssues.length === 0) {
    console.warn('No issues found - writing empty index');
    writeFileSync(OUTPUT_PATH, JSON.stringify({ issues: [], embeddings: [] }));
    return;
  }

  console.log(`Loading model: ${MODEL_NAME}`);
  const extractor = await pipeline('feature-extraction', MODEL_NAME, {
    dtype: 'fp32',
  });

  const texts = allIssues.map((issue) => issueText(issue.title, issue.body));
  const allEmbeddings = [];

  console.log(`Generating embeddings for ${texts.length} issues...`);
  for (let i = 0; i < texts.length; i += BATCH_SIZE) {
    const batch = texts.slice(i, i + BATCH_SIZE);
    const output = await extractor(batch, {
      pooling: 'mean',
      normalize: true,
    });

    const vectors = output.tolist();
    allEmbeddings.push(...vectors);

    const progress = Math.min(i + BATCH_SIZE, texts.length);
    console.log(`  ${progress}/${texts.length}`);
  }

  const issueMetadata = allIssues.map((issue) => {
    const body = (issue.body || '').trim();
    return {
      number: issue.number,
      title: issue.title,
      state: issue.state,
      url: issue.html_url,
      body_preview: body.slice(0, 500) || '',
      labels: (issue.labels || []).map((l) => l.name),
      created_at: issue.created_at,
      updated_at: issue.updated_at,
    };
  });

  const indexData = {
    issues: issueMetadata,
    embeddings: allEmbeddings,
    model: MODEL_NAME,
    issue_count: issueMetadata.length,
    built_at: new Date().toISOString(),
  };

  const dir = dirname(OUTPUT_PATH);
  if (dir && dir !== '.') mkdirSync(dir, { recursive: true });
  writeFileSync(OUTPUT_PATH, JSON.stringify(indexData));

  const sizeMb = (
    Buffer.byteLength(JSON.stringify(indexData)) /
    (1024 * 1024)
  ).toFixed(1);
  console.log(
    `Index saved to ${OUTPUT_PATH} (${sizeMb} MB, ${issueMetadata.length} issues)`
  );
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});


================================================
FILE: bin/duplicate-detector/detect.mjs
================================================
#!/usr/bin/env node
/**
 * Duplicate Issue Detector
 *
 * Triggered on new issue creation. Compares the new issue against an
 * existing embedding index, then uses an LLM to
 * confirm duplicates before posting a comment for maintainer review.
 */

import { pipeline } from '@huggingface/transformers';
import { existsSync, readFileSync } from 'node:fs';
import {
  addLabel,
  dotProduct,
  fetchIssues,
  getIssue,
  issueText,
  postComment,
} from './utils.mjs';

const SIMILARITY_THRESHOLD = 0.55;
const TOP_K = 5;
const MAX_COMMENT_CANDIDATES = 3;
const MODEL_NAME = process.env.EMBEDDING_MODEL || 'Xenova/all-MiniLM-L6-v2';
const GROQ_MODEL = process.env.GROQ_MODEL || 'llama-3.3-70b-versatile';
const INDEX_PATH = 'issue_index.json';
const LABEL_NAME = 'possible-duplicate';

const GROQ_API_KEY = process.env.GROQ_API_KEY || '';
const ISSUE_NUMBER = parseInt(process.env.ISSUE_NUMBER, 10);

function loadIndex(path) {
  if (!existsSync(path)) {
    console.error(
      `Index file not found at ${path}. Run build-index.mjs first.`
    );
    process.exit(1);
  }

  const data = JSON.parse(readFileSync(path, 'utf-8'));
  console.log(`Loaded index with ${data.issues.length} issues`);
  return data;
}

function findSimilar(
  queryEmbedding,
  index,
  { topK = TOP_K, threshold = SIMILARITY_THRESHOLD, excludeNumber } = {}
) {
  const { issues, embeddings } = index;
  if (!issues.length) return [];

  const scored = issues.map((issue, i) => ({
    ...issue,
    score: dotProduct(queryEmbedding, embeddings[i]),
  }));

  return scored
    .sort((a, b) => b.score - a.score)
    .filter(
      (c) =>
        c.score >= threshold && (!excludeNumber || c.number !== excludeNumber)
    )
    .slice(0, topK);
}

const CONFIRM_SYSTEM_PROMPT = `You are a GitHub issue triage assistant. You will be given a NEW issue and one \
or more CANDIDATE issues that may be duplicates.

For each candidate, determine if the new issue is truly a duplicate (same root \
problem/request) or merely related (similar area but different issue).

Respond ONLY with a JSON array of objects, each with:
- "number": the candidate issue number
- "duplicate": true or false
- "reason": one-sentence explanation

Example:
[{"number": 123, "duplicate": true, "reason": "Both report the same crash when ..."}]`;

async function confirmWithLlm(newIssue, candidates) {
  if (!GROQ_API_KEY) {
    console.warn('GROQ_API_KEY not set — skipping LLM confirmation');
    return candidates;
  }

  const candidateText = candidates
    .map(
      (c) =>
        `### Candidate #${c.number} (similarity: ${c.score.toFixed(2)})\n` +
        `**Title:** ${c.title}\n` +
        `**State:** ${c.state}\n` +
        `**Body preview:** ${(c.body_preview || 'N/A').slice(0, 500)}`
    )
    .join('\n\n');

  const userPrompt =
    `## NEW ISSUE #${newIssue.number}\n` +
    `**Title:** ${newIssue.title}\n` +
    `**Body:**\n${(newIssue.body || 'No body').slice(0, 1500)}\n\n` +
    `---\n\n` +
    `## CANDIDATES\n${candidateText}`;

  try {
    const resp = await fetch(
      'https://api.groq.com/openai/v1/chat/completions',
      {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${GROQ_API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          model: GROQ_MODEL,
          messages: [
            { role: 'system', content: CONFIRM_SYSTEM_PROMPT },
            { role: 'user', content: userPrompt },
          ],
          temperature: 0.1,
          max_tokens: 1024,
        }),
        signal: AbortSignal.timeout(30_000),
      }
    );

    if (!resp.ok) {
      const text = await resp.text();
      throw new Error(`Groq API error ${resp.status}: ${text}`);
    }

    let content = (await resp.json()).choices[0].message.content.trim();

    if (content.startsWith('```')) {
      content = content
        .split('\n')
        .slice(1)
        .join('\n')
        .replace(/```\s*$/, '')
        .trim();
    }

    const verdicts = JSON.parse(content);
    if (!Array.isArray(verdicts)) {
      throw new Error('Invalid LLM response format - expected array');
    }

    const verdictMap = new Map(verdicts.map((v) => [v.number, v]));

    const confirmed = [];
    for (const c of candidates) {
      const verdict = verdictMap.get(c.number);
      if (verdict?.duplicate) {
        c.llm_reason = verdict.reason || '';
        confirmed.push(c);
      } else {
        const reason = verdict?.reason || 'not evaluated';
        console.log(`  #${c.number} ruled out by LLM: ${reason}`);
      }
    }

    return confirmed;
  } catch (err) {
    console.warn(
      `LLM confirmation failed: ${err.message} - falling back to all candidates`
    );
    return candidates;
  }
}

function formatComment(candidates) {
  const lines = [
    '**Possible duplicate detected**',
    '',
    'This issue may be a duplicate of the following (detected via semantic similarity + LLM review):',
    '',
  ];

  for (const c of candidates.slice(0, MAX_COMMENT_CANDIDATES)) {
    const confidence = `${(c.score * 100).toFixed(0)}%`;
    let line = `- #${c.number} (${confidence} match) — ${c.title}`;
    if (c.llm_reason) {
      line += `\n  > *${c.llm_reason}*`;
    }
    lines.push(line);
  }

  lines.push(
    '',
    'A maintainer will review this. If this is **not** a duplicate, no action is needed.',
    '',
    `<!-- duplicate-bot: candidates=${candidates.map((c) => c.number).join(',')} -->`
  );

  return lines.join('\n');
}

async function main() {
  if (!ISSUE_NUMBER) {
    console.error('ISSUE_NUMBER not set');
    process.exit(1);
  }

  console.log(`Processing issue #${ISSUE_NUMBER}`);
  const issue = await getIssue(ISSUE_NUMBER);

  const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
  const recentIssues = await fetchIssues({
    creator: issue.user.login,
    since: oneHourAgo,
    state: 'all',
  });

  if (recentIssues.length > 10) {
    console.log(
      `User ${issue.user.login} created ${recentIssues.length} issues in the last hour - skipping to prevent spam`
    );
    return;
  }

  if (issue.pull_request) {
    console.log('Skipping - this is a pull request');
    return;
  }

  if (issue.user.type === 'Bot') {
    console.log('Skipping - issue created by bot');
    return;
  }

  console.log(`Loading model: ${MODEL_NAME}`);
  const extractor = await pipeline('feature-extraction', MODEL_NAME, {
    dtype: 'fp32',
  });
  const index = loadIndex(INDEX_PATH);

  const text = issueText(issue.title, issue.body);
  const output = await extractor(text, { pooling: 'mean', normalize: true });
  const queryEmbedding = output.tolist()[0];

  let candidates = findSimilar(queryEmbedding, index, {
    topK: TOP_K,
    threshold: SIMILARITY_THRESHOLD,
    excludeNumber: issue.number,
  });

  if (!candidates.length) {
    console.log('No similar issues found above threshold - done');
    return;
  }

  console.log(`Found ${candidates.length} candidates above threshold:`);
  for (const c of candidates) {
    console.log(`  #${c.number} (${c.score.toFixed(3)}) - ${c.title}`);
  }

  console.log('Running LLM confirmation via Groq...');
  candidates = await confirmWithLlm(issue, candidates);

  if (!candidates.length) {
    console.log('LLM ruled out all candidates - done');
    return;
  }

  const comment = formatComment(candidates);
  await postComment(ISSUE_NUMBER, comment);
  await addLabel(ISSUE_NUMBER, LABEL_NAME);

  console.log('Done!');
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});


================================================
FILE: bin/duplicate-detector/package.json
================================================
{
  "name": "duplicate-detector",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "packageManager": "pnpm@10.17.1",
  "scripts": {
    "build-index": "node build-index.mjs",
    "detect": "node detect.mjs"
  },
  "dependencies": {
    "@huggingface/transformers": "^3.8.1"
  },
  "engines": {
    "node": ">=22.0"
  }
}


================================================
FILE: bin/duplicate-detector/utils.mjs
================================================
const GITHUB_API = 'https://api.github.com';
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
const GITHUB_REPOSITORY = process.env.GITHUB_REPOSITORY;

function ghHeaders() {
  return {
    Authorization: `token ${GITHUB_TOKEN}`,
    Accept: 'application/vnd.github+json',
  };
}

export async function fetchIssues({
  state = 'open',
  since,
  maxIssues = 5000,
} = {}) {
  const issues = [];
  let page = 1;
  const perPage = 100;

  while (issues.length < maxIssues) {
    const params = new URLSearchParams({
      state,
      per_page: String(perPage),
      page: String(page),
      sort: 'updated',
      direction: 'desc',
    });
    if (since) params.set('since', since);

    const url = `${GITHUB_API}/repos/${GITHUB_REPOSITORY}/issues?${params}`;
    const resp = await fetch(url, { headers: ghHeaders() });

    if (!resp.ok) {
      throw new Error(`GitHub API error: ${resp.status} ${resp.statusText}`);
    }

    const batch = await resp.json();
    if (!batch.length) break;

    for (const item of batch) {
      if (!item.pull_request) {
        issues.push(item);
      }
    }

    page++;
    if (batch.length < perPage) break;
  }

  return issues.slice(0, maxIssues);
}

export async function getIssue(issueNumber) {
  const url = `${GITHUB_API}/repos/${GITHUB_REPOSITORY}/issues/${issueNumber}`;
  const resp = await fetch(url, { headers: ghHeaders() });

  if (!resp.ok) {
    throw new Error(`GitHub API error: ${resp.status} ${resp.statusText}`);
  }

  return resp.json();
}

export async function postComment(issueNumber, body) {
  const url = `${GITHUB_API}/repos/${GITHUB_REPOSITORY}/issues/${issueNumber}/comments`;
  const resp = await fetch(url, {
    method: 'POST',
    headers: { ...ghHeaders(), 'Content-Type': 'application/json' },
    body: JSON.stringify({ body }),
  });

  if (!resp.ok) {
    throw new Error(
      `Failed to post comment: ${resp.status} ${resp.statusText}`
    );
  }

  console.log(`Posted comment on #${issueNumber}`);
}

export async function addLabel(issueNumber, label) {
  const url = `${GITHUB_API}/repos/${GITHUB_REPOSITORY}/issues/${issueNumber}/labels`;
  const resp = await fetch(url, {
    method: 'POST',
    headers: { ...ghHeaders(), 'Content-Type': 'application/json' },
    body: JSON.stringify({ labels: [label] }),
  });

  if (resp.status === 404) {
    console.warn(
      `Label '${label}' does not exist - skipping. Create it manually.`
    );
    return;
  }

  if (!resp.ok) {
    throw new Error(`Failed to add label: ${resp.status} ${resp.statusText}`);
  }

  console.log(`Added label '${label}' to #${issueNumber}`);
}

export function issueText(title, body) {
  body = (body || '').trim();
  if (body.length > 2000) body = body.slice(0, 2000) + '...';
  return body ? `${title}\n\n${body}` : title;
}

export function dotProduct(a, b) {
  let sum = 0;
  for (let i = 0; i < a.length; i++) {
    sum += a[i] * b[i];
  }
  return sum;
}


================================================
FILE: bin/prepare.js
================================================
#!/usr/bin/env node

/**
 * Do not run husky in CI environments
 */
const isCi = process.env.CI !== undefined;
if (!isCi) {
  require('husky').install();
}


================================================
FILE: charts/seerr-chart/.helmignore
================================================
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
# go template
*.gotmpl


================================================
FILE: charts/seerr-chart/Chart.yaml
================================================
apiVersion: v2
kubeVersion: '>=1.23.0-0'
name: seerr-chart
description: Seerr helm chart for Kubernetes
type: application
version: 3.3.0
# renovate: image=ghcr.io/seerr-team/seerr
appVersion: 'v3.1.0'
maintainers:
  - name: Seerr Team
    url: https://github.com/orgs/seerr-team/people
sources:
  - https://github.com/seerr-team/seerr/tree/main/charts/seerr
home: https://github.com/seerr-team/seerr


================================================
FILE: charts/seerr-chart/README.md
================================================
# seerr-chart

![Version: 3.3.0](https://img.shields.io/badge/Version-3.3.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v3.1.0](https://img.shields.io/badge/AppVersion-v3.1.0-informational?style=flat-square)

Seerr helm chart for Kubernetes

**Homepage:** <https://github.com/seerr-team/seerr>

## Maintainers

| Name | Email | Url |
| ---- | ------ | --- |
| Seerr Team |  | <https://github.com/orgs/seerr-team/people> |

## Source Code

* <https://github.com/seerr-team/seerr/tree/main/charts/seerr>

## Requirements

Kubernetes: `>=1.23.0-0`

## Installation

Refer to [Seerr kubernetes documentation](https://docs.seerr.dev/getting-started/kubernetes)

## Update Notes

### Updating to 3.0.0

Nothing has changed; we just rebranded the `jellyseerr` Helm chart to `seerr` 🥳 refer to our [Migration guide](https://docs.seerr.dev/migration-guide).

### Updating to 2.7.0

Seerr is a stateful application and it is not designed to have multiple replicas. In version 2.7.0 we address this by:

- replacing `Deployment` with `StatefulSet`
- removing `replicaCount` value

If `replicaCount` value was used - remove it. Helm update should work fine after that.

## Values

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| affinity | object | `{}` |  |
| config | object | `{"persistence":{"accessModes":["ReadWriteOnce"],"annotations":{},"existingClaim":"","name":"","size":"5Gi","volumeName":""}}` | Creating PVC to store configuration |
| config.persistence.accessModes | list | `["ReadWriteOnce"]` | Access modes of persistent disk |
| config.persistence.annotations | object | `{}` | Annotations for PVCs |
| config.persistence.existingClaim | string | `""` | Specify an existing `PersistentVolumeClaim` to use. If this value is provided, the default PVC will not be created |
| config.persistence.name | string | `""` | Config name |
| config.persistence.size | string | `"5Gi"` | Size of persistent disk |
| config.persistence.volumeName | string | `""` | Name of the permanent volume to reference in the claim. Can be used to bind to existing volumes. |
| extraEnv | list | `[]` | Environment variables to add to the seerr pods |
| extraEnvFrom | list | `[]` | Environment variables from secrets or configmaps to add to the seerr pods |
| fullnameOverride | string | `""` |  |
| image.pullPolicy | string | `"IfNotPresent"` |  |
| image.registry | string | `"ghcr.io"` |  |
| image.repository | string | `"seerr-team/seerr"` |  |
| image.sha | string | `""` |  |
| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. |
| imagePullSecrets | list | `[]` |  |
| ingress.annotations | object | `{}` |  |
| ingress.enabled | bool | `false` |  |
| ingress.hosts[0].host | string | `"chart-example.local"` |  |
| ingress.hosts[0].paths[0].path | string | `"/"` |  |
| ingress.hosts[0].paths[0].pathType | string | `"ImplementationSpecific"` |  |
| ingress.ingressClassName | string | `""` |  |
| ingress.tls | list | `[]` |  |
| nameOverride | string | `""` |  |
| nodeSelector | object | `{}` |  |
| podAnnotations | object | `{}` |  |
| podLabels | object | `{}` |  |
| podSecurityContext.fsGroup | int | `1000` |  |
| podSecurityContext.fsGroupChangePolicy | string | `"OnRootMismatch"` |  |
| probes.livenessProbe | object | `{}` | Configure liveness probe |
| probes.readinessProbe | object | `{}` | Configure readiness probe |
| probes.startupProbe | string | `nil` | Configure startup probe |
| resources | object | `{}` |  |
| route.main.additionalRules | list | `[]` |  |
| route.main.annotations | object | `{}` |  |
| route.main.apiVersion | string | `"gateway.networking.k8s.io/v1"` | Set the route apiVersion, e.g. gateway.networking.k8s.io/v1 or gateway.networking.k8s.io/v1alpha2 |
| route.main.enabled | bool | `false` | Enables or disables the Gateway API route |
| route.main.filters | list | `[]` |  |
| route.main.hostnames | list | `[]` |  |
| route.main.httpsRedirect | bool | `false` | To redirect to HTTPS, create a new route object under the main route and enable this option. This should only be used with HTTP-like routes, such as HTTPRoute or GRPCRoute. [Reference]( https://gateway-api.sigs.k8s.io/guides/http-redirect-rewrite/ ) |
| route.main.kind | string | `"HTTPRoute"` | Set the route kind. Note that experimental kinds require changing `apiVersion` |
| route.main.labels | object | `{}` |  |
| route.main.matches[0].path.type | string | `"PathPrefix"` |  |
| route.main.matches[0].path.value | string | `"/"` |  |
| route.main.parentRefs | list | `[]` |  |
| securityContext.allowPrivilegeEscalation | bool | `false` |  |
| securityContext.capabilities.drop[0] | string | `"ALL"` |  |
| securityContext.privileged | bool | `false` |  |
| securityContext.readOnlyRootFilesystem | bool | `false` |  |
| securityContext.runAsGroup | int | `1000` |  |
| securityContext.runAsNonRoot | bool | `true` |  |
| securityContext.runAsUser | int | `1000` |  |
| securityContext.seccompProfile.type | string | `"RuntimeDefault"` |  |
| service.port | int | `80` |  |
| service.type | string | `"ClusterIP"` |  |
| serviceAccount.annotations | object | `{}` | Annotations to add to the service account |
| serviceAccount.automount | bool | `true` | Automatically mount a ServiceAccount's API credentials? |
| serviceAccount.create | bool | `true` | Specifies whether a service account should be created |
| serviceAccount.name | string | `""` | If not set and create is true, a name is generated using the fullname template |
| tolerations | list | `[]` |  |
| volumeMounts | list | `[]` | Additional volumeMounts on the output StatefulSet definition. |
| volumes | list | `[]` | Additional volumes on the output StatefulSet definition. |


================================================
FILE: charts/seerr-chart/README.md.gotmpl
================================================
{{ template "chart.header" . }}

{{ template "chart.deprecationWarning" . }}

{{ template "chart.badgesSection" . }}

{{ template "chart.description" . }}

{{ template "chart.homepageLine" . }}

{{ template "chart.maintainersSection" . }}

{{ template "chart.sourcesSection" . }}

{{ template "chart.requirementsSection" . }}

## Installation

Refer to [Seerr kubernetes documentation](https://docs.seerr.dev/getting-started/kubernetes)

## Update Notes

### Updating to 3.0.0

Nothing has changed; we just rebranded the `jellyseerr` Helm chart to `seerr` 🥳 refer to our [Migration guide](https://docs.seerr.dev/migration-guide).

### Updating to 2.7.0

Seerr is a stateful application and it is not designed to have multiple replicas. In version 2.7.0 we address this by:

- replacing `Deployment` with `StatefulSet`
- removing `replicaCount` value

If `replicaCount` value was used - remove it. Helm update should work fine after that.

{{ template "chart.valuesSection" . }}


================================================
FILE: charts/seerr-chart/artifacthub-repo.yml
================================================
repositoryID: 249547ec-2a30-48de-a5bc-07bfd5aa2e8f


================================================
FILE: charts/seerr-chart/templates/NOTES.txt
================================================
***********************************************************************
 Welcome to {{ .Chart.Name }}
 Chart version: {{ .Chart.Version }}
 App   version: {{ .Chart.AppVersion }}
***********************************************************************

================================================
FILE: charts/seerr-chart/templates/_helpers.tpl
================================================
{{/*
Expand the name of the chart.
*/}}
{{- define "seerr.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "seerr.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "seerr.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "seerr.labels" -}}
helm.sh/chart: {{ include "seerr.chart" . }}
{{ include "seerr.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/part-of: {{ .Chart.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "seerr.selectorLabels" -}}
app.kubernetes.io/name: {{ include "seerr.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
Create the name of the service account to use
*/}}
{{- define "seerr.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "seerr.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

{{/*
Create the name of the pvc config to use
*/}}
{{- define "seerr.configPersistenceName" -}}
{{- default (printf "%s-config" (include "seerr.fullname" .)) .Values.config.persistence.name }}
{{- end }}


================================================
FILE: charts/seerr-chart/templates/ingress.yaml
================================================
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "seerr.fullname" . }}
  labels:
    {{- include "seerr.labels" . | nindent 4 }}
  {{- with .Values.ingress.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
  {{- if .Values.ingress.ingressClassName }}
  ingressClassName: {{ .Values.ingress.ingressClassName }}
  {{- end }}
  {{- if .Values.ingress.tls }}
  tls:
    {{- range .Values.ingress.tls }}
    - hosts:
        {{- range .hosts }}
        - {{ . | quote }}
        {{- end }}
      secretName: {{ .secretName }}
    {{- end }}
  {{- end }}
  rules:
    {{- range .Values.ingress.hosts }}
    - host: {{ .host | quote }}
      http:
        paths:
          {{- range .paths }}
          - path: {{ .path }}
            pathType: {{ .pathType }}
            backend:
              service:
                name: {{ include "seerr.fullname" $ }}
                port:
                  number: {{ $.Values.service.port }}
          {{- end }}
    {{- end }}
{{- end }}


================================================
FILE: charts/seerr-chart/templates/persistentvolumeclaim.yaml
================================================
{{- if not .Values.config.persistence.existingClaim -}}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: {{ include "seerr.configPersistenceName" . }}
  labels:
    {{- include "seerr.labels" . | nindent 4 }}
  {{- with .Values.config.persistence.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
  {{- with .Values.config.persistence.accessModes }}
  accessModes:
    {{- toYaml . | nindent 4 }}
  {{- end }}
  {{- if .Values.config.persistence.volumeName }}
  volumeName: {{ .Values.config.persistence.volumeName }}
  {{- end }}
  {{- with .Values.config.persistence.storageClass }}
  storageClassName: {{ if (eq "-" .) }}""{{ else }}{{ . }}{{ end }}
  {{- end }}
  resources:
    requests:
      storage: "{{ .Values.config.persistence.size }}"
{{- end -}}

================================================
FILE: charts/seerr-chart/templates/route.yaml
================================================
{{- range $name, $route := .Values.route }}
{{- if $route.enabled }}
---
apiVersion: {{ $route.apiVersion | default "gateway.networking.k8s.io/v1" }}
kind: {{ $route.kind | default "HTTPRoute" }}
metadata:
  name: {{ template "seerr.fullname" $ }}{{ if ne $name "main" }}-{{ $name }}{{ end }}
  labels:
    {{- include "seerr.labels" $ | nindent 4 }}
    {{- with $route.labels }}
    {{- toYaml . | nindent 4 }}
    {{- end }}
  {{- with $route.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
  {{- with $route.parentRefs }}
  parentRefs:
    {{- toYaml . | nindent 4 }}
  {{- end }}
  {{- with $route.hostnames }}
  hostnames:
    {{- tpl (toYaml .) $ | nindent 4 }}
  {{- end }}
  rules:
    {{- with $route.additionalRules }}
    {{- tpl (toYaml .) $ | nindent 4 }}
    {{- end }}
    {{- if $route.httpsRedirect }}
    - filters:
        - type: RequestRedirect
          requestRedirect:
            scheme: https
            statusCode: 301
    {{- else }}
    - backendRefs:
        - name: {{ include "seerr.fullname" $ }}
          port: {{ $.Values.service.port }}
      {{- with $route.filters }}
      filters:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with $route.matches }}
      matches:
        {{- toYaml . | nindent 8 }}
      {{- end }}
    {{- end }}
{{- end }}
{{- end }}

================================================
FILE: charts/seerr-chart/templates/service.yaml
================================================
apiVersion: v1
kind: Service
metadata:
  name: {{ include "seerr.fullname" . }}
  labels:
    {{- include "seerr.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: http
      protocol: TCP
      name: http
  selector:
    {{- include "seerr.selectorLabels" . | nindent 4 }}
  ipFamilyPolicy: PreferDualStack


================================================
FILE: charts/seerr-chart/templates/serviceaccount.yaml
================================================
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ include "seerr.serviceAccountName" . }}
  labels:
    {{- include "seerr.labels" . | nindent 4 }}
  {{- with .Values.serviceAccount.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
{{- end }}


================================================
FILE: charts/seerr-chart/templates/statefulset.yaml
================================================
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ include "seerr.fullname" . }}
  labels:
    {{- include "seerr.labels" . | nindent 4 }}
spec:
  serviceName: {{ include "seerr.fullname" . }}
  selector:
    matchLabels:
      {{- include "seerr.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      {{- with .Values.podAnnotations }}
      annotations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      labels:
        {{- include "seerr.labels" . | nindent 8 }}
        {{- with .Values.podLabels }}
        {{- toYaml . | nindent 8 }}
        {{- end }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "seerr.serviceAccountName" . }}
      securityContext:
        {{- toYaml .Values.podSecurityContext | nindent 8 }}
      containers:
        - name: {{ .Chart.Name }}
          securityContext:
            {{- toYaml .Values.securityContext | nindent 12 }}
          {{- if .Values.image.sha }}
          image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}@sha256:{{ .Values.image.sha }}"
          {{- else }}
          image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          {{- end }}
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: 5055
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
            {{- if .Values.probes.livenessProbe.initialDelaySeconds }}
            initialDelaySeconds: {{ .Values.probes.livenessProbe.initialDelaySeconds }}
            {{- end }}
            {{- if .Values.probes.livenessProbe.periodSeconds }}
            periodSeconds: {{ .Values.probes.livenessProbe.periodSeconds }}
            {{- end }}
            {{- if .Values.probes.livenessProbe.timeoutSeconds }}
            timeoutSeconds: {{ .Values.probes.livenessProbe.timeoutSeconds }}
            {{- end }}
            {{- if .Values.probes.livenessProbe.successThreshold }}
            successThreshold: {{ .Values.probes.livenessProbe.successThreshold }}
            {{- end }}
            {{- if .Values.probes.livenessProbe.failureThreshold }}
            failureThreshold: {{ .Values.probes.livenessProbe.failureThreshold }}
            {{- end }}
          readinessProbe:
            httpGet:
              path: /
              port: http
            {{- if .Values.probes.readinessProbe.initialDelaySeconds }}
            initialDelaySeconds: {{ .Values.probes.readinessProbe.initialDelaySeconds }}
            {{- end }}
            {{- if .Values.probes.readinessProbe.periodSeconds }}
            periodSeconds: {{ .Values.probes.readinessProbe.periodSeconds }}
            {{- end }}
            {{- if .Values.probes.readinessProbe.timeoutSeconds }}
            timeoutSeconds: {{ .Values.probes.readinessProbe.timeoutSeconds }}
            {{- end }}
            {{- if .Values.probes.readinessProbe.successThreshold }}
            successThreshold: {{ .Values.probes.readinessProbe.successThreshold }}
            {{- end }}
            {{- if .Values.probes.readinessProbe.failureThreshold }}
            failureThreshold: {{ .Values.probes.readinessProbe.failureThreshold }}
            {{- end }}
          {{- if .Values.probes.startupProbe }}
          startupProbe:
            {{- toYaml .Values.probes.startupProbe | nindent 12 }}
          {{- end }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          {{- with .Values.extraEnv }}
          env:
            {{- toYaml . | nindent 12 }}
          {{- end }}
          {{- with .Values.extraEnvFrom }}
          envFrom:
            {{- toYaml . | nindent 12 }}
          {{- end }}
          volumeMounts:
            - name: config
              mountPath: /app/config
          {{- with .Values.volumeMounts }}
            {{- toYaml . | nindent 12 }}
          {{- end }}
      volumes:
        - name: config
          persistentVolumeClaim:
            claimName: {{ if .Values.config.persistence.existingClaim }}{{ .Values.config.persistence.existingClaim }}{{- else }}{{ include "seerr.configPersistenceName" . }}{{- end }}
      {{- with .Values.volumes }}
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}


================================================
FILE: charts/seerr-chart/templates/tests/test-connection.yaml
================================================
apiVersion: v1
kind: Pod
metadata:
  name: "{{ include "seerr.fullname" . }}-test-connection"
  labels:
    {{- include "seerr.labels" . | nindent 4 }}
  annotations:
    "helm.sh/hook": test
spec:
  containers:
    - name: wget
      image: busybox
      command: ['wget']
      args: ['{{ include "seerr.fullname" . }}:{{ .Values.service.port }}']
  restartPolicy: Never


================================================
FILE: charts/seerr-chart/values.yaml
================================================
image:
  registry: ghcr.io
  repository: seerr-team/seerr
  pullPolicy: IfNotPresent
  # -- Overrides the image tag whose default is the chart appVersion.
  tag: ''
  sha: ''

imagePullSecrets: []
nameOverride: ''
fullnameOverride: ''

# Liveness / Readiness / Startup Probes
probes:
  # -- Configure liveness probe
  livenessProbe: {}
  # initialDelaySeconds: 60
  # periodSeconds: 30
  # timeoutSeconds: 5
  # successThreshold: 1
  # failureThreshold: 5
  # -- Configure readiness probe
  readinessProbe: {}
  # initialDelaySeconds: 60
  # periodSeconds: 30
  # timeoutSeconds: 5
  # successThreshold: 1
  # failureThreshold: 5
  # -- Configure startup probe
  startupProbe: null
  #  tcpSocket:
  #    port: http

# -- Environment variables to add to the seerr pods
extraEnv: []
# -- Environment variables from secrets or configmaps to add to the seerr pods
extraEnvFrom: []

serviceAccount:
  # -- Specifies whether a service account should be created
  create: true
  # -- Automatically mount a ServiceAccount's API credentials?
  automount: true
  # -- Annotations to add to the service account
  annotations: {}
  # -- The name of the service account to use.
  # -- If not set and create is true, a name is generated using the fullname template
  name: ''

podAnnotations: {}
podLabels: {}

podSecurityContext:
  fsGroup: 1000
  fsGroupChangePolicy: OnRootMismatch

securityContext:
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL
  readOnlyRootFilesystem: false
  runAsNonRoot: true
  privileged: false
  runAsUser: 1000
  runAsGroup: 1000
  seccompProfile:
    type: RuntimeDefault

service:
  type: ClusterIP
  port: 80

# -- Creating PVC to store configuration
config:
  persistence:
    # -- Size of persistent disk
    size: 5Gi
    # -- Annotations for PVCs
    annotations: {}
    # -- Access modes of persistent disk
    accessModes:
      - ReadWriteOnce
    # -- Config name
    name: ''
    # -- Name of the permanent volume to reference in the claim.
    # Can be used to bind to existing volumes.
    volumeName: ''
    # -- Specify an existing `PersistentVolumeClaim` to use. If this value is provided, the default PVC will not be created
    existingClaim: ''

ingress:
  enabled: false
  ingressClassName: ''
  annotations: {}
  #  kubernetes.io/ingress.class: nginx
  #  kubernetes.io/tls-acme: "true"
  hosts:
    - host: chart-example.local
      paths:
        - path: /
          pathType: ImplementationSpecific
  tls: []
  #  - secretName: chart-example-tls
  #    hosts:
  #      - chart-example.local

route:
  main:
    # -- Enables or disables the Gateway API route
    enabled: false

    # -- Set the route apiVersion, e.g. gateway.networking.k8s.io/v1 or gateway.networking.k8s.io/v1alpha2
    apiVersion: gateway.networking.k8s.io/v1

    # -- Set the route kind.
    # Note that experimental kinds require changing `apiVersion`
    kind: HTTPRoute

    annotations: {}

    labels: {}

    parentRefs: []
    # - name: my-gateway
    #   namespace: gateway
    #   sectionName: https

    hostnames: []
    # - seerr.example.com

    additionalRules: []

    filters: []

    matches:
      - path:
          type: PathPrefix
          value: /

    # -- To redirect to HTTPS, create a new route object under the main route and enable this option.
    # This should only be used with HTTP-like routes, such as HTTPRoute or GRPCRoute.
    # [Reference]( https://gateway-api.sigs.k8s.io/guides/http-redirect-rewrite/ )
    httpsRedirect: false

resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
#   cpu: 100m
#   memory: 128Mi
# requests:
#   cpu: 100m
#   memory: 128Mi

# -- Additional volumes on the output StatefulSet definition.
volumes: []
# - name: foo
#   secret:
#     secretName: mysecret
#     optional: false

# -- Additional volumeMounts on the output StatefulSet definition.
volumeMounts: []
# - name: foo
#   mountPath: "/etc/foo"
#   readOnly: true

nodeSelector: {}

tolerations: []

affinity: {}


================================================
FILE: compose.postgres.yaml
================================================
services:
  seerr:
    build:
      context: .
      dockerfile: Dockerfile.local
    ports:
      - '5055:5055'
    environment:
      DB_TYPE: 'postgres' # Which DB engine to use. The default is "sqlite". To use postgres, this needs to be set to "postgres"
      DB_HOST: 'postgres' # The host (url) of the database
      DB_PORT: '5432' # The port to connect to
      DB_USER: 'seerr' # Username used to connect to the database
      DB_PASS: 'seerr' # Password of the user used to connect to the database
      DB_NAME: 'seerr' # The name of the database to connect to
      DB_LOG_QUERIES: 'false' # Whether to log the DB queries for debugging
      DB_USE_SSL: 'false' # Whether to enable ssl for database connection
    volumes:
      - .:/app:rw,cached
      - /app/node_modules
      - /app/.next
    depends_on:
      - postgres
    links:
      - postgres
  postgres:
    image: postgres:18
    environment:
      POSTGRES_USER: seerr
      POSTGRES_PASSWORD: seerr
      POSTGRES_DB: seerr
    ports:
      - '5432:5432'
    volumes:
      - postgres:/var/lib/postgresql
volumes:
  postgres:


================================================
FILE: compose.yaml
================================================
services:
  seerr:
    build:
      context: .
      dockerfile: Dockerfile.local
    ports:
      - 5055:5055
    volumes:
      - .:/app:rw,cached
      - /app/node_modules
      - /app/.next


================================================
FILE: config/.gitkeep
================================================


================================================
FILE: config/db/.gitkeep
================================================


================================================
FILE: config/logs/.gitkeep
================================================


================================================
FILE: cypress/config/settings.cypress.json
================================================
{
  "clientId": "6919275e-142a-48d8-be6b-93594cbd4626",
  "vapidPrivate": "tmnslaO8ZWN6bNbSEv_rolPeBTlNxOwCCAHrM9oZz3M",
  "vapidPublic": "BK_EpP8NDm9waor2zn6_S28o3ZYv4kCkJOfYpO3pt3W6jnPmxrgTLANUBNbbyaNatPnSQ12De9CeqSYQrqWzHTs",
  "main": {
    "apiKey": "testkey",
    "applicationTitle": "Seerr",
    "applicationUrl": "",
    "cacheImages": false,
    "defaultPermissions": 32,
    "defaultQuotas": {
      "movie": {},
      "tv": {}
    },
    "hideAvailable": false,
    "localLogin": true,
    "newPlexLogin": true,
    "discoverRegion": "",
    "streamingRegion": "",
    "originalLanguage": "",
    "blocklistedTags": "",
    "blocklistedTagsLimit": 50,
    "trustProxy": false,
    "mediaServerType": 1,
    "partialRequestsEnabled": true,
    "enableSpecialEpisodes": false,
    "locale": "en"
  },
  "plex": {
    "name": "Seerr",
    "ip": "192.168.1.1",
    "port": 32400,
    "useSsl": false,
    "libraries": [
      {
        "id": "1",
        "name": "Movies",
        "enabled": true,
        "type": "movie"
      }
    ],
    "machineId": "test"
  },
  "jellyfin": {
    "name": "",
    "ip": "",
    "port": 8096,
    "useSsl": false,
    "urlBase": "",
    "externalHostname": "",
    "jellyfinForgotPasswordUrl": "",
    "libraries": [],
    "serverId": ""
  },
  "tautulli": {},
  "radarr": [],
  "sonarr": [],
  "public": {
    "initialized": true
  },
  "notifications": {
    "agents": {
      "email": {
        "enabled": false,
        "options": {
          "emailFrom": "",
          "smtpHost": "",
          "smtpPort": 587,
          "secure": false,
          "ignoreTls": false,
          "requireTls": false,
          "allowSelfSigned": false,
          "senderName": "Seerr"
        }
      },
      "discord": {
        "enabled": false,
        "types": 0,
        "options": {
          "webhookUrl": "",
          "webhookRoleId": "",
          "enableMentions": true
        }
      },
      "slack": {
        "enabled": false,
        "types": 0,
        "options": {
          "webhookUrl": ""
        }
      },
      "telegram": {
        "enabled": false,
        "types": 0,
        "options": {
          "botAPI": "",
          "chatId": "",
          "messageThreadId": "",
          "sendSilently": false
        }
      },
      "pushbullet": {
        "enabled": false,
        "types": 0,
        "options": {
          "accessToken": ""
        }
      },
      "pushover": {
        "enabled": false,
        "types": 0,
        "options": {
          "accessToken": "",
          "userToken": ""
        }
      },
      "webhook": {
        "enabled": false,
        "types": 0,
        "options": {
          "webhookUrl": "",
          "jsonPayload": "IntcbiAgICBcIm5vdGlmaWNhdGlvbl90eXBlXCI6IFwie3tub3RpZmljYXRpb25fdHlwZX19XCIsXG4gICAgXCJldmVudFwiOiBcInt7ZXZlbnR9fVwiLFxuICAgIFwic3ViamVjdFwiOiBcInt7c3ViamVjdH19XCIsXG4gICAgXCJtZXNzYWdlXCI6IFwie3ttZXNzYWdlfX1cIixcbiAgICBcImltYWdlXCI6IFwie3tpbWFnZX19XCIsXG4gICAgXCJ7e21lZGlhfX1cIjoge1xuICAgICAgICBcIm1lZGlhX3R5cGVcIjogXCJ7e21lZGlhX3R5cGV9fVwiLFxuICAgICAgICBcInRtZGJJZFwiOiBcInt7bWVkaWFfdG1kYmlkfX1cIixcbiAgICAgICAgXCJ0dmRiSWRcIjogXCJ7e21lZGlhX3R2ZGJpZH19XCIsXG4gICAgICAgIFwic3RhdHVzXCI6IFwie3ttZWRpYV9zdGF0dXN9fVwiLFxuICAgICAgICBcInN0YXR1czRrXCI6IFwie3ttZWRpYV9zdGF0dXM0a319XCJcbiAgICB9LFxuICAgIFwie3tyZXF1ZXN0fX1cIjoge1xuICAgICAgICBcInJlcXVlc3RfaWRcIjogXCJ7e3JlcXVlc3RfaWR9fVwiLFxuICAgICAgICBcInJlcXVlc3RlZEJ5X2VtYWlsXCI6IFwie3tyZXF1ZXN0ZWRCeV9lbWFpbH19XCIsXG4gICAgICAgIFwicmVxdWVzdGVkQnlfdXNlcm5hbWVcIjogXCJ7e3JlcXVlc3RlZEJ5X3VzZXJuYW1lfX1cIixcbiAgICAgICAgXCJyZXF1ZXN0ZWRCeV9hdmF0YXJcIjogXCJ7e3JlcXVlc3RlZEJ5X2F2YXRhcn19XCJcbiAgICB9LFxuICAgIFwie3tpc3N1ZX19XCI6IHtcbiAgICAgICAgXCJpc3N1ZV9pZFwiOiBcInt7aXNzdWVfaWR9fVwiLFxuICAgICAgICBcImlzc3VlX3R5cGVcIjogXCJ7e2lzc3VlX3R5cGV9fVwiLFxuICAgICAgICBcImlzc3VlX3N0YXR1c1wiOiBcInt7aXNzdWVfc3RhdHVzfX1cIixcbiAgICAgICAgXCJyZXBvcnRlZEJ5X2VtYWlsXCI6IFwie3tyZXBvcnRlZEJ5X2VtYWlsfX1cIixcbiAgICAgICAgXCJyZXBvcnRlZEJ5X3VzZXJuYW1lXCI6IFwie3tyZXBvcnRlZEJ5X3VzZXJuYW1lfX1cIixcbiAgICAgICAgXCJyZXBvcnRlZEJ5X2F2YXRhclwiOiBcInt7cmVwb3J0ZWRCeV9hdmF0YXJ9fVwiXG4gICAgfSxcbiAgICBcInt7Y29tbWVudH19XCI6IHtcbiAgICAgICAgXCJjb21tZW50X21lc3NhZ2VcIjogXCJ7e2NvbW1lbnRfbWVzc2FnZX19XCIsXG4gICAgICAgIFwiY29tbWVudGVkQnlfZW1haWxcIjogXCJ7e2NvbW1lbnRlZEJ5X2VtYWlsfX1cIixcbiAgICAgICAgXCJjb21tZW50ZWRCeV91c2VybmFtZVwiOiBcInt7Y29tbWVudGVkQnlfdXNlcm5hbWV9fVwiLFxuICAgICAgICBcImNvbW1lbnRlZEJ5X2F2YXRhclwiOiBcInt7Y29tbWVudGVkQnlfYXZhdGFyfX1cIlxuICAgIH0sXG4gICAgXCJ7e2V4dHJhfX1cIjogW11cbn0i"
        }
      },
      "webpush": {
        "enabled": false,
        "options": {}
      },
      "gotify": {
        "enabled": false,
        "types": 0,
        "options": {
          "url": "",
          "token": "",
          "priority": 0
        }
      },
      "ntfy": {
        "enabled": false,
        "types": 0,
        "options": {
          "url": "",
          "topic": ""
        }
      }
    }
  },
  "jobs": {
    "plex-recently-added-scan": {
      "schedule": "0 */5 * * * *"
    },
    "plex-full-scan": {
      "schedule": "0 0 3 * * *"
    },
    "radarr-scan": {
      "schedule": "0 0 4 * * *"
    },
    "sonarr-scan": {
      "schedule": "0 30 4 * * *"
    },
    "plex-watchlist-sync": {
      "schedule": "0 */10 * * * *"
    },
    "availability-sync": {
      "schedule": "0 0 5 * * *"
    },
    "download-sync": {
      "schedule": "0 * * * * *"
    },
    "download-sync-reset": {
      "schedule": "0 0 1 * * *"
    },
    "jellyfin-recently-added-scan": {
      "schedule": "0 */5 * * * *"
    },
    "jellyfin-full-scan": {
      "schedule": "0 0 3 * * *"
    },
    "image-cache-cleanup": {
      "schedule": "0 0 5 * * *"
    }
  },
  "network": {
    "csrfProtection": false,
    "trustProxy": false,
    "forceIpv4First": false,
    "dnsServers": "",
    "proxy": {
      "enabled": false,
      "hostname": "",
      "port": 8080,
      "useSsl": false,
      "user": "",
      "password": "",
      "bypassFilter": "",
      "bypassLocalAddresses": true
    },
    "dnsCache": {
      "enabled": false,
      "forceMinTtl": 0,
      "forceMaxTtl": -1
    }
  }
}


================================================
FILE: cypress/e2e/discover.cy.ts
================================================
const clickFirstTitleCardInSlider = (sliderTitle: string): void => {
  cy.contains('.slider-header', sliderTitle)
    .next('[data-testid=media-slider]')
    .find('[data-testid=title-card]')
    .first()
    .trigger('mouseover')
    .find('[data-testid=title-card-title]')
    .invoke('text')
    .then((text) => {
      cy.contains('.slider-header', sliderTitle)
        .next('[data-testid=media-slider]')
        .find('[data-testid=title-card]')
        .first()
        .click();
      cy.get('[data-testid=media-title]').should('contain', text);
    });
};

describe('Discover', () => {
  beforeEach(() => {
    cy.loginAsAdmin();
  });

  it('loads a trending item', () => {
    cy.intercept('/api/v1/discover/trending*').as('getTrending');
    cy.visit('/');
    cy.wait('@getTrending');
    clickFirstTitleCardInSlider('Trending');
  });

  it('loads popular movies', () => {
    cy.intercept('/api/v1/discover/movies*').as('getPopularMovies');
    cy.visit('/');
    cy.wait('@getPopularMovies');
    clickFirstTitleCardInSlider('Popular Movies');
  });

  it('loads upcoming movies', () => {
    cy.intercept('/api/v1/discover/movies?page=1&primaryReleaseDateGte*').as(
      'getUpcomingMovies'
    );
    cy.visit('/');
    cy.wait('@getUpcomingMovies');
    clickFirstTitleCardInSlider('Upcoming Movies');
  });

  it('loads popular series', () => {
    cy.intercept('/api/v1/discover/tv*').as('getPopularTv');
    cy.visit('/');
    cy.wait('@getPopularTv');
    clickFirstTitleCardInSlider('Popular Series');
  });

  it('loads upcoming series', () => {
    cy.intercept('/api/v1/discover/tv?page=1&firstAirDateGte=*').as(
      'getUpcomingSeries'
    );
    cy.visit('/');
    cy.wait('@getUpcomingSeries');
    clickFirstTitleCardInSlider('Upcoming Series');
  });

  it('displays error for media with invalid TMDB ID', () => {
    cy.intercept('GET', '/api/v1/media?*', {
      pageInfo: { pages: 1, pageSize: 20, results: 1, page: 1 },
      results: [
        {
          downloadStatus: [],
          downloadStatus4k: [],
          id: 1922,
          mediaType: 'movie',
          tmdbId: 998814,
          tvdbId: null,
          imdbId: null,
          status: 5,
          status4k: 1,
          createdAt: '2022-08-18T18:11:13.000Z',
          updatedAt: '2022-08-18T19:56:41.000Z',
          lastSeasonChange: '2022-08-18T19:56:41.000Z',
          mediaAddedAt: '2022-08-18T19:56:41.000Z',
          serviceId: null,
          serviceId4k: null,
          externalServiceId: null,
          externalServiceId4k: null,
          externalServiceSlug: null,
          externalServiceSlug4k: null,
          ratingKey: null,
          ratingKey4k: null,
          seasons: [],
        },
      ],
    }).as('getMedia');

    cy.visit('/');
    cy.wait('@getMedia');
    cy.contains('.slider-header', 'Recently Added')
      .next('[data-testid=media-slider]')
      .find('[data-testid=title-card]')
      .first()
      .find('[data-testid=title-card-title]')
      .contains('Movie Not Found');
  });

  it('displays error for request with invalid TMDB ID', () => {
    cy.intercept('GET', '/api/v1/request?*', {
      pageInfo: { pages: 1, pageSize: 10, results: 1, page: 1 },
      results: [
        {
          id: 582,
          status: 1,
          createdAt: '2022-08-18T18:11:13.000Z',
          updatedAt: '2022-08-18T18:11:13.000Z',
          type: 'movie',
          is4k: false,
          serverId: null,
          profileId: null,
          rootFolder: null,
          languageProfileId: null,
          tags: null,
          media: {
            downloadStatus: [],
            downloadStatus4k: [],
            id: 1922,
            mediaType: 'movie',
            tmdbId: 998814,
            tvdbId: null,
            imdbId: null,
            status: 2,
            status4k: 1,
            createdAt: '2022-08-18T18:11:13.000Z',
            updatedAt: '2022-08-18T18:11:13.000Z',
            lastSeasonChange: '2022-08-18T18:11:13.000Z',
            mediaAddedAt: null,
            serviceId: null,
            serviceId4k: null,
            externalServiceId: null,
            externalServiceId4k: null,
            externalServiceSlug: null,
            externalServiceSlug4k: null,
            ratingKey: null,
            ratingKey4k: null,
          },
          seasons: [],
          modifiedBy: null,
          requestedBy: {
            permissions: 4194336,
            id: 18,
            email: 'friend@seerr.dev',
            plexUsername: null,
            username: '',
            recoveryLinkExpirationDate: null,
            userType: 2,
            avatar:
              'https://gravatar.com/avatar/c77fdc27cab83732b8623d2ea873d330?default=mm&size=200',
            movieQuotaLimit: null,
            movieQuotaDays: null,
            tvQuotaLimit: null,
            tvQuotaDays: null,
            createdAt: '2022-08-17T04:55:28.000Z',
            updatedAt: '2022-08-17T04:55:28.000Z',
            requestCount: 1,
            displayName: 'friend@seerr.dev',
          },
          seasonCount: 0,
        },
      ],
    }).as('getRequests');

    cy.visit('/');
    cy.wait('@getRequests');
    cy.contains('.slider-header', 'Recent Requests')
      .next('[data-testid=media-slider]')
      .find('[data-testid=request-card]')
      .first()
      .find('[data-testid=request-card-title]')
      .contains('Movie Not Found');
  });

  it('loads plex watchlist', () => {
    cy.intercept('/api/v1/discover/watchlist', {
      fixture: 'watchlist.json',
    }).as('getWatchlist');
    // Wait for one of the watchlist movies to resolve
    cy.intercept('/api/v1/movie/361743').as('getTmdbMovie');

    cy.visit('/');

    cy.wait('@getWatchlist');

    const sliderHeader = cy.contains('.slider-header', 'Watchlist');

    sliderHeader.scrollIntoView();

    cy.wait('@getTmdbMovie');
    // Wait a little longer to make sure the movie component reloaded
    cy.wait(500);

    sliderHeader
      .next('[data-testid=media-slider]')
      .find('[data-testid=title-card]')
      .first()
      .trigger('mouseover')
      .find('[data-testid=title-card-title]')
      .invoke('text')
      .then((text) => {
        cy.contains('.slider-header', 'Watchlist')
          .next('[data-testid=media-slider]')
          .find('[data-testid=title-card]')
          .first()
          .click();
        cy.get('[data-testid=media-title]').should('contain', text);
      });
  });
});


================================================
FILE: cypress/e2e/login.cy.ts
================================================
describe('Login Page', () => {
  it('succesfully logs in as an admin', () => {
    cy.loginAsAdmin();
    cy.visit('/');
    cy.contains('Trending');
  });

  it('succesfully logs in as a local user', () => {
    cy.loginAsUser();
    cy.visit('/');
    cy.contains('Trending');
  });
});


================================================
FILE: cypress/e2e/movie-details.cy.ts
================================================
describe('Movie Details', () => {
  it('loads a movie page', () => {
    cy.loginAsAdmin();
    // Try to load minions: rise of gru
    cy.visit('/movie/438148');

    cy.get('[data-testid=media-title]').should(
      'contain',
      'Minions: The Rise of Gru (2022)'
    );
  });
});


================================================
FILE: cypress/e2e/providers/tvdb.cy.ts
================================================
describe('TVDB Integration', () => {
  // Constants for routes and selectors
  const ROUTES = {
    home: '/',
    metadataSettings: '/settings/metadata',
    tomorrowIsOursTvShow: '/tv/72879',
    monsterTvShow: '/tv/225634',
    dragonnBallZKaiAnime: '/tv/61709',
  };

  const SELECTORS = {
    sidebarToggle: '[data-testid=sidebar-toggle]',
    sidebarSettingsMobile: '[data-testid=sidebar-menu-settings-mobile]',
    settingsNavDesktop: 'nav[data-testid="settings-nav-desktop"]',
    metadataTestButton: 'button[type="button"]:contains("Test")',
    metadataSaveButton: '[data-testid="metadata-save-button"]',
    tmdbStatus: '[data-testid="tmdb-status"]',
    tvdbStatus: '[data-testid="tvdb-status"]',
    tvMetadataProviderSelector: '[data-testid="tv-metadata-provider-selector"]',
    animeMetadataProviderSelector:
      '[data-testid="anime-metadata-provider-selector"]',
    seasonSelector: '[data-testid="season-selector"]',
    season1: 'Season 1',
    season2: 'Season 2',
    season3: 'Season 3',
    episodeList: '[data-testid="episode-list"]',
    episode9: '9 - Hang Men',
  };

  // Reusable commands
  const navigateToMetadataSettings = () => {
    cy.visit(ROUTES.home);
    cy.get(SELECTORS.sidebarToggle).click();
    cy.get(SELECTORS.sidebarSettingsMobile).click();
    cy.get(
      `${SELECTORS.settingsNavDesktop} a[href="${ROUTES.metadataSettings}"]`
    ).click();
  };

  const testAndVerifyMetadataConnection = () => {
    cy.intercept('POST', '/api/v1/settings/metadatas/test').as(
      'testConnection'
    );
    cy.get(SELECTORS.metadataTestButton).click();
    return cy.wait('@testConnection');
  };

  const saveMetadataSettings = (customBody = null) => {
    if (customBody) {
      cy.intercept('PUT', '/api/v1/settings/metadatas', (req) => {
        req.body = customBody;
      }).as('saveMetadata');
    } else {
      // Else just intercept without modifying body
      cy.intercept('PUT', '/api/v1/settings/metadatas').as('saveMetadata');
    }

    cy.get(SELECTORS.metadataSaveButton).click();
    return cy.wait('@saveMetadata');
  };

  beforeEach(() => {
    // Perform login
    cy.login(Cypress.env('ADMIN_EMAIL'), Cypress.env('ADMIN_PASSWORD'));

    // Navigate to Metadata settings
    navigateToMetadataSettings();

    // Verify we're on the correct settings page
    cy.contains('h3', 'Metadata Providers').should('be.visible');

    // Configure TVDB as TV provider and test connection
    cy.get(SELECTORS.tvMetadataProviderSelector).click();

    // get id react-select-4-option-1
    cy.get('[class*="react-select__option"]').contains('TheTVDB').click();

    // Test the connection
    testAndVerifyMetadataConnection().then(({ response }) => {
      expect(response.statusCode).to.equal(200);
      // Check TVDB connection status
      cy.get(SELECTORS.tvdbStatus).should('contain', 'Operational');
    });

    // Save settings
    saveMetadataSettings({
      anime: 'tvdb',
      tv: 'tvdb',
    }).then(({ response }) => {
      expect(response.statusCode).to.equal(200);
      expect(response.body.tv).to.equal('tvdb');
    });
  });

  it('should display "Tomorrow is Ours" show information with multiple seasons from TVDB', () => {
    // Navigate to the TV show
    cy.visit(ROUTES.tomorrowIsOursTvShow);

    // Verify that multiple seasons are displayed (TMDB has only 1 season, TVDB has multiple)
    // cy.get(SELECTORS.seasonSelector).should('exist');
    cy.intercept('/api/v1/tv/225634/season/1').as('season1');
    // Select Season 2 and verify it loads
    cy.contains(SELECTORS.season2)
      .should('be.visible')
      .scrollIntoView()
      .click();

    // Verify that episodes are displayed for Season 2
    cy.contains('260 - Episode 506').should('be.visible');
  });

  it('Should display "Monster" show information correctly when not existing on TVDB', () => {
    // Navigate to the TV show
    cy.visit(ROUTES.monsterTvShow);

    // Intercept season 1 request
    cy.intercept('/api/v1/tv/225634/season/1').as('season1');

    // Select Season 1
    cy.contains(SELECTORS.season1)
      .should('be.visible')
      .scrollIntoView()
      .click();

    // Wait for the season data to load
    cy.wait('@season1');

    // Verify specific episode exists
    cy.contains(SELECTORS.episode9).should('be.visible');
  });

  it('should display "Dragon Ball Z Kai" show information with multiple only 2 seasons from TVDB', () => {
    // Navigate to the TV show
    cy.visit(ROUTES.dragonnBallZKaiAnime);

    // Intercept season 1 request
    cy.intercept('/api/v1/tv/61709/season/1').as('season1');

    // Select Season 2 and verify it visible
    cy.contains(SELECTORS.season2)
      .should('be.visible')
      .scrollIntoView()
      .click();

    // select season 3 and verify it not visible
    cy.contains(SELECTORS.season3).should('not.exist');
  });
});


================================================
FILE: cypress/e2e/pull-to-refresh.cy.ts
================================================
describe('Pull To Refresh', () => {
  beforeEach(() => {
    cy.login(Cypress.env('ADMIN_EMAIL'), Cypress.env('ADMIN_PASSWORD'));
    cy.viewport(390, 844);
    cy.visitMobile('/');
  });

  it('reloads the current page', () => {
    cy.wait(500);

    cy.intercept({
      method: 'GET',
      url: '/api/v1/*',
    }).as('apiCall');

    cy.get('.searchbar').swipe('bottom', [190, 500]);

    cy.wait('@apiCall').then((interception) => {
      assert.isNotNull(
        interception.response.body,
        'API was called and received data'
      );
    });
  });
});


================================================
FILE: cypress/e2e/settings/discover-customization.cy.ts
================================================
describe('Discover Customization', () => {
  beforeEach(() => {
    cy.loginAsAdmin();
    cy.intercept('/api/v1/settings/discover').as('getDiscoverSliders');
  });

  it('show the discover customization settings', () => {
    cy.visit('/');

    cy.get('[data-testid=discover-start-editing]').click();

    cy.get('[data-testid=create-slider-header')
      .should('contain', 'Create New Slider')
      .scrollIntoView();

    // There should be some built in options
    cy.get('[data-testid=discover-slider-edit-mode]').should(
      'contain',
      'Recently Added'
    );
    cy.get('[data-testid=discover-slider-edit-mode]').should(
      'contain',
      'Recent Requests'
    );
  });

  it('can drag to re-order elements and save to persist the changes', () => {
    let dataTransfer = new DataTransfer();
    cy.visit('/');

    cy.get('[data-testid=discover-start-editing]').click();

    cy.get('[data-testid=discover-slider-edit-mode]')
      .first()
      .trigger('dragstart', { dataTransfer });
    cy.get('[data-testid=discover-slider-edit-mode]')
      .eq(1)
      .trigger('drop', { dataTransfer });
    cy.get('[data-testid=discover-slider-edit-mode]')
      .eq(1)
      .trigger('dragend', { dataTransfer });

    cy.get('[data-testid=discover-slider-edit-mode]')
      .eq(1)
      .should('contain', 'Recently Added');

    cy.get('[data-testid=discover-customize-submit').click();
    cy.wait('@getDiscoverSliders');

    cy.reload();

    cy.get('[data-testid=discover-start-editing]').click();

    dataTransfer = new DataTransfer();

    cy.get('[data-testid=discover-slider-edit-mode]')
      .eq(1)
      .should('contain', 'Recently Added');

    cy.get('[data-testid=discover-slider-edit-mode]')
      .first()
      .trigger('dragstart', { dataTransfer });
    cy.get('[data-testid=discover-slider-edit-mode]')
      .eq(1)
      .trigger('drop', { dataTransfer });
    cy.get('[data-testid=discover-slider-edit-mode]')
      .eq(1)
      .trigger('dragend', { dataTransfer });

    cy.get('[data-testid=discover-slider-edit-mode]')
      .eq(1)
      .should('contain', 'Recent Requests');

    cy.get('[data-testid=discover-customize-submit').click();
    cy.wait('@getDiscoverSliders');
  });

  it('can create a new discover option and remove it', () => {
    cy.visit('/');
    cy.intercept('/api/v1/settings/discover/*').as('discoverSlider');
    cy.intercept('/api/v1/search/keyword*').as('searchKeyword');

    cy.get('[data-testid=discover-start-editing]').click();

    const sliderTitle = 'Custom Keyword Slider';

    cy.get('#sliderType').select('TMDB Movie Keyword');

    cy.get('#title').type(sliderTitle);
    // First confirm that an invalid keyword doesn't allow us to submit anything
    cy.get('#data').type('invalidkeyword{enter}', { delay: 100 });
    cy.wait('@searchKeyword');

    cy.get('[data-testid=create-discover-option-form]')
      .find('button')
      .should('be.disabled');

    cy.get('#data').clear();
    cy.get('#data').type('christmas{enter}', { delay: 100 });

    // Confirming we have some results
    cy.contains('.slider-header', sliderTitle)
      .next('[data-testid=media-slider]')
      .find('[data-testid=title-card]');

    cy.get('[data-testid=create-discover-option-form]').submit();

    cy.wait('@discoverSlider');
    cy.wait('@getDiscoverSliders');
    cy.wait(1000);

    cy.get('[data-testid=discover-slider-edit-mode]')
      .first()
      .should('contain', sliderTitle);

    // Make sure its still there even if we reload
    cy.reload();

    cy.get('[data-testid=discover-start-editing]').click();

    cy.get('[data-testid=discover-slider-edit-mode]')
      .first()
      .should('contain', sliderTitle);

    // Verify it's not rendering on our discover page (its still disabled!)
    cy.visit('/');

    cy.get('.slider-header').should('not.contain', sliderTitle);

    cy.get('[data-testid=discover-start-editing]').click();

    // Enable it, and check again
    cy.get('[data-testid=discover-slider-edit-mode]')
      .first()
      .find('[role="checkbox"]')
      .click();

    cy.get('[data-testid=discover-customize-submit').click();
    cy.wait('@getDiscoverSliders');

    cy.visit('/');

    cy.contains('.slider-header', sliderTitle)
      .next('[data-testid=media-slider]')
      .find('[data-testid=title-card]');

    cy.get('[data-testid=discover-start-editing]').click();

    // let's delete it and confirm its deleted.
    cy.get('[data-testid=discover-slider-edit-mode]')
      .first()
      .find('[data-testid=discover-slider-remove-button]')
      .click();

    cy.wait('@discoverSlider');
    cy.wait('@getDiscoverSliders');
    cy.wait(1000);

    cy.get('[data-testid=discover-slider-edit-mode]')
      .first()
      .should('not.contain', sliderTitle);
  });
});


================================================
FILE: cypress/e2e/settings/general-settings.cy.ts
================================================
describe('General Settings', () => {
  beforeEach(() => {
    cy.loginAsAdmin();
  });

  it('opens the settings page from the home page', () => {
    cy.visit('/');

    cy.get('[data-testid=sidebar-toggle]').click();
    cy.get('[data-testid=sidebar-menu-settings-mobile]').click();

    cy.get('.heading').should('contain', 'General Settings');
  });

  it('modifies setting that requires restart', () => {
    cy.visit('/settings/network');

    cy.get('#trustProxy').click();
    cy.get('[data-testid=settings-network-form]').submit();
    cy.get('[data-testid=modal-title]').should(
      'contain',
      'Server Restart Required'
    );

    cy.get('[data-testid=modal-ok-button]').click();
    cy.get('[data-testid=modal-title]').should('not.exist');

    cy.get('[type=checkbox]#trustProxy').click();
    cy.get('[data-testid=settings-network-form]').submit();
    cy.get('[data-testid=modal-title]').should('not.exist');
  });
});


================================================
FILE: cypress/e2e/tv-details.cy.ts
================================================
describe('TV Details', () => {
  it('loads a tv details page', () => {
    cy.loginAsAdmin();
    // Try to load stranger things
    cy.visit('/tv/66732');

    cy.get('[data-testid=media-title]').should(
      'contain',
      'Stranger Things (2016)'
    );
  });

  it('shows seasons and expands episodes', () => {
    cy.loginAsAdmin();

    // Try to load stranger things
    cy.visit('/tv/66732');

    // intercept request for season info
    cy.intercept('/api/v1/tv/66732/season/4').as('season4');

    cy.contains('Season 4').should('be.visible').scrollIntoView().click();

    cy.wait('@season4');

    cy.contains('Chapter Nine').should('be.visible');
  });
});


================================================
FILE: cypress/e2e/user/auto-request-settings.cy.ts
================================================
const visitUserEditPage = (email: string): void => {
  cy.visit('/users');

  cy.contains('[data-testid=user-list-row]', email).contains('Edit').click();
};

describe('Auto Request Settings', () => {
  beforeEach(() => {
    cy.loginAsAdmin();
  });

  it('should not see watchlist sync settings on an account without permissions', () => {
    visitUserEditPage(Cypress.env('USER_EMAIL'));

    cy.contains('Auto-Request Movies').should('not.exist');
    cy.contains('Auto-Request Series').should('not.exist');
  });

  it('should see watchlist sync settings on an admin account', () => {
    visitUserEditPage(Cypress.env('ADMIN_EMAIL'));

    cy.contains('Auto-Request Movies').should('exist');
    cy.contains('Auto-Request Series').should('exist');
  });

  it('should see auto-request settings after being given permission', () => {
    visitUserEditPage(Cypress.env('USER_EMAIL'));

    cy.get('[data-testid=settings-nav-desktop').contains('Permissions').click();

    cy.get('#autorequest').should('not.be.checked').click();

    cy.intercept('/api/v1/user/*/settings/permissions').as('userPermissions');

    cy.contains('Save Changes').click();

    cy.wait('@userPermissions');

    cy.reload();

    cy.get('#autorequest').should('be.checked');
    cy.get('#autorequestmovies').should('be.checked');
    cy.get('#autorequesttv').should('be.checked');

    cy.get('[data-testid=settings-nav-desktop').contains('General').click();

    cy.contains('Auto-Request Movies').should('exist');
    cy.contains('Auto-Request Series').should('exist');

    cy.get('#watchlistSyncMovies').should('not.be.checked').click();
    cy.get('#watchlistSyncTv').should('not.be.checked').click();

    cy.intercept('/api/v1/user/*/settings/main').as('userMain');

    cy.contains('Save Changes').click();

    cy.wait('@userMain');

    cy.reload();

    cy.get('#watchlistSyncMovies').should('be.checked').click();
    cy.get('#watchlistSyncTv').should('be.checked').click();

    cy.contains('Save Changes').click();

    cy.wait('@userMain');

    cy.get('[data-testid=settings-nav-desktop').contains('Permissions').click();

    cy.get('#autorequest').should('be.checked').click();

    cy.contains('Save Changes').click();
  });
});


================================================
FILE: cypress/e2e/user/profile.cy.ts
================================================
describe('User Profile', () => {
  beforeEach(() => {
    cy.loginAsAdmin();
  });

  it('opens user profile page from the home page', () => {
    cy.visit('/');

    cy.get('[data-testid=user-menu]').click();
    cy.get('[data-testid=user-menu-profile]').click();

    cy.get('h1').should('contain', Cypress.env('ADMIN_EMAIL'));
  });

  it('loads plex watchlist', () => {
    cy.intercept('/api/v1/user/[0-9]*/watchlist', {
      fixture: 'watchlist.json',
    }).as('getWatchlist');
    // Wait for one of the watchlist movies to resolve
    cy.intercept('/api/v1/movie/361743').as('getTmdbMovie');

    cy.visit('/profile');

    cy.wait('@getWatchlist');

    const sliderHeader = cy.contains('.slider-header', 'Plex Watchlist');

    sliderHeader.scrollIntoView();

    cy.wait('@getTmdbMovie');
    // Wait a little longer to make sure the movie component reloaded
    cy.wait(500);

    sliderHeader
      .next('[data-testid=media-slider]')
      .find('[data-testid=title-card]')
      .first()
      .trigger('mouseover')
      .find('[data-testid=title-card-title]')
      .invoke('text')
      .then((text) => {
        cy.contains('.slider-header', 'Plex Watchlist')
          .next('[data-testid=media-slider]')
          .find('[data-testid=title-card]')
          .first()
          .click();
        cy.get('[data-testid=media-title]').should('contain', text);
      });
  });
});


================================================
FILE: cypress/e2e/user/user-list.cy.ts
================================================
const testUser = {
  username: 'Test User',
  emailAddress: 'test@seeerr.dev',
  password: 'test1234',
};

describe('User List', () => {
  beforeEach(() => {
    cy.loginAsAdmin();
  });

  it('opens the user list from the home page', () => {
    cy.visit('/');

    cy.get('[data-testid=sidebar-toggle]').click();
    cy.get('[data-testid=sidebar-menu-users-mobile]').click();

    cy.get('[data-testid=page-header]').should('contain', 'User List');
  });

  it('can find the admin user and friend user in the user list',
Download .txt
gitextract_udf17ict/

├── .dockerignore
├── .editorconfig
├── .gitattributes
├── .github/
│   ├── CODEOWNERS
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug.yml
│   │   ├── config.yml
│   │   ├── documentation.yml
│   │   ├── enhancement.yml
│   │   └── maintenance.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── cliff.toml
│   ├── renovate/
│   │   ├── actions.json5
│   │   ├── docker.json5
│   │   ├── groups.json5
│   │   ├── helm.json5
│   │   ├── labels.json5
│   │   ├── pnpm.json5
│   │   └── semanticCommits.json5
│   ├── renovate.json5
│   └── workflows/
│       ├── ci.yml
│       ├── codeql.yml
│       ├── conflict_labeler.yml
│       ├── create-tag.yml
│       ├── cypress.yml
│       ├── detect-duplicate.yml
│       ├── docs-deploy.yml
│       ├── docs-link-check.yml
│       ├── helm.yml
│       ├── lint-helm-charts.yml
│       ├── preview.yml
│       ├── rebuild-issue-index.yml
│       ├── release.yml
│       ├── renovate-helm-custom-hooks.yml
│       ├── seerr-labeller.yml
│       ├── semantic-pr.yml
│       ├── stale.yml
│       ├── test-docs-deploy.yml
│       └── trivy-scan.yml
├── .gitignore
├── .husky/
│   ├── commit-msg
│   ├── pre-commit
│   └── prepare-commit-msg
├── .npmrc
├── .prettierignore
├── .prettierrc.js
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── Dockerfile.local
├── LICENSE
├── README.md
├── SECURITY.md
├── bin/
│   ├── check-i18n.js
│   ├── duplicate-detector/
│   │   ├── .gitignore
│   │   ├── build-index.mjs
│   │   ├── detect.mjs
│   │   ├── package.json
│   │   └── utils.mjs
│   └── prepare.js
├── charts/
│   └── seerr-chart/
│       ├── .helmignore
│       ├── Chart.yaml
│       ├── README.md
│       ├── README.md.gotmpl
│       ├── artifacthub-repo.yml
│       ├── templates/
│       │   ├── NOTES.txt
│       │   ├── _helpers.tpl
│       │   ├── ingress.yaml
│       │   ├── persistentvolumeclaim.yaml
│       │   ├── route.yaml
│       │   ├── service.yaml
│       │   ├── serviceaccount.yaml
│       │   ├── statefulset.yaml
│       │   └── tests/
│       │       └── test-connection.yaml
│       └── values.yaml
├── compose.postgres.yaml
├── compose.yaml
├── config/
│   ├── .gitkeep
│   ├── db/
│   │   └── .gitkeep
│   └── logs/
│       └── .gitkeep
├── cypress/
│   ├── config/
│   │   └── settings.cypress.json
│   ├── e2e/
│   │   ├── discover.cy.ts
│   │   ├── login.cy.ts
│   │   ├── movie-details.cy.ts
│   │   ├── providers/
│   │   │   └── tvdb.cy.ts
│   │   ├── pull-to-refresh.cy.ts
│   │   ├── settings/
│   │   │   ├── discover-customization.cy.ts
│   │   │   └── general-settings.cy.ts
│   │   ├── tv-details.cy.ts
│   │   └── user/
│   │       ├── auto-request-settings.cy.ts
│   │       ├── profile.cy.ts
│   │       └── user-list.cy.ts
│   ├── fixtures/
│   │   └── watchlist.json
│   ├── support/
│   │   ├── commands.ts
│   │   ├── e2e.ts
│   │   └── index.ts
│   └── tsconfig.json
├── cypress.config.ts
├── docs/
│   ├── README.md
│   ├── extending-seerr/
│   │   ├── _category_.json
│   │   ├── database-config.mdx
│   │   └── reverse-proxy.mdx
│   ├── getting-started/
│   │   ├── _category_.json
│   │   ├── buildfromsource.mdx
│   │   ├── docker.mdx
│   │   ├── index.mdx
│   │   ├── kubernetes.mdx
│   │   └── third-parties/
│   │       ├── aur.mdx
│   │       ├── index.mdx
│   │       ├── nixpkg.mdx
│   │       ├── synology.mdx
│   │       ├── truenas.mdx
│   │       └── unraid.mdx
│   ├── migration-guide.mdx
│   ├── troubleshooting.mdx
│   └── using-seerr/
│       ├── _category_.json
│       ├── advanced/
│       │   ├── index.mdx
│       │   └── verifying-signed-artifacts.mdx
│       ├── backups.md
│       ├── notifications/
│       │   ├── discord.md
│       │   ├── email.md
│       │   ├── gotify.md
│       │   ├── index.mdx
│       │   ├── ntfy.md
│       │   ├── pushbullet.md
│       │   ├── pushover.md
│       │   ├── slack.md
│       │   ├── telegram.md
│       │   ├── webhook.md
│       │   └── webpush.md
│       ├── plex/
│       │   ├── _category_.json
│       │   ├── index.md
│       │   └── watchlist-auto-request.md
│       ├── settings/
│       │   ├── _category_.json
│       │   ├── dns-caching.md
│       │   ├── general.md
│       │   ├── jobs&cache.md
│       │   ├── mediaserver.mdx
│       │   ├── notifications.mdx
│       │   ├── services.md
│       │   └── users.md
│       └── users/
│           ├── _category_.json
│           ├── adding-users.mdx
│           ├── deleting-users.md
│           ├── editing-users.md
│           └── owner.md
├── eslint.config.mts
├── gen-docs/
│   ├── .gitignore
│   ├── README.md
│   ├── babel.config.js
│   ├── blog/
│   │   ├── 2025-09-29-introducing-seerr-blog.md
│   │   ├── 2026-02-10/
│   │   │   └── seerr-release.md
│   │   ├── 2026-02-28-seerr-security-fix-release.md
│   │   └── authors.yml
│   ├── docusaurus.config.ts
│   ├── package.json
│   ├── sidebars.ts
│   ├── src/
│   │   ├── components/
│   │   │   └── SeerrVersion/
│   │   │       └── index.tsx
│   │   └── css/
│   │       └── custom.css
│   ├── static/
│   │   ├── .nojekyll
│   │   └── CNAME
│   ├── tailwind.config.js
│   └── tsconfig.json
├── next-env.d.ts
├── next.config.js
├── package.json
├── postcss.config.js
├── public/
│   ├── offline.html
│   ├── robots.txt
│   ├── site.webmanifest
│   └── sw.js
├── seerr-api.yml
├── server/
│   ├── api/
│   │   ├── animelist.ts
│   │   ├── externalapi.ts
│   │   ├── github.ts
│   │   ├── jellyfin.ts
│   │   ├── metadata.ts
│   │   ├── plexapi.ts
│   │   ├── plextv.ts
│   │   ├── provider.ts
│   │   ├── pushover.ts
│   │   ├── rating/
│   │   │   ├── imdbRadarrProxy.ts
│   │   │   └── rottentomatoes.ts
│   │   ├── ratings.ts
│   │   ├── servarr/
│   │   │   ├── base.ts
│   │   │   ├── radarr.ts
│   │   │   └── sonarr.ts
│   │   ├── tautulli.ts
│   │   ├── themoviedb/
│   │   │   ├── constants.ts
│   │   │   ├── index.ts
│   │   │   └── interfaces.ts
│   │   └── tvdb/
│   │       ├── index.ts
│   │       └── interfaces.ts
│   ├── constants/
│   │   ├── discover.ts
│   │   ├── error.ts
│   │   ├── issue.ts
│   │   ├── media.ts
│   │   ├── server.ts
│   │   └── user.ts
│   ├── datasource.ts
│   ├── entity/
│   │   ├── Blocklist.ts
│   │   ├── DiscoverSlider.ts
│   │   ├── Issue.ts
│   │   ├── IssueComment.ts
│   │   ├── Media.ts
│   │   ├── MediaRequest.ts
│   │   ├── OverrideRule.ts
│   │   ├── Season.ts
│   │   ├── SeasonRequest.ts
│   │   ├── Session.ts
│   │   ├── User.ts
│   │   ├── UserPushSubscription.ts
│   │   ├── UserSettings.ts
│   │   └── Watchlist.ts
│   ├── index.ts
│   ├── interfaces/
│   │   └── api/
│   │       ├── blocklistInterfaces.ts
│   │       ├── common.ts
│   │       ├── discoverInterfaces.ts
│   │       ├── issueInterfaces.ts
│   │       ├── mediaInterfaces.ts
│   │       ├── overrideRuleInterfaces.ts
│   │       ├── personInterfaces.ts
│   │       ├── plexInterfaces.ts
│   │       ├── requestInterfaces.ts
│   │       ├── serviceInterfaces.ts
│   │       ├── settingsInterfaces.ts
│   │       ├── userInterfaces.ts
│   │       ├── userSettingsInterfaces.ts
│   │       └── watchlistCreate.ts
│   ├── job/
│   │   ├── blocklistedTagsProcessor.ts
│   │   └── schedule.ts
│   ├── lib/
│   │   ├── availabilitySync.ts
│   │   ├── cache.ts
│   │   ├── downloadtracker.ts
│   │   ├── email/
│   │   │   ├── index.ts
│   │   │   └── openpgpEncrypt.ts
│   │   ├── imageproxy.ts
│   │   ├── notifications/
│   │   │   ├── agents/
│   │   │   │   ├── agent.ts
│   │   │   │   ├── discord.ts
│   │   │   │   ├── email.ts
│   │   │   │   ├── gotify.ts
│   │   │   │   ├── ntfy.ts
│   │   │   │   ├── pushbullet.ts
│   │   │   │   ├── pushover.ts
│   │   │   │   ├── slack.ts
│   │   │   │   ├── telegram.ts
│   │   │   │   ├── webhook.ts
│   │   │   │   └── webpush.ts
│   │   │   └── index.ts
│   │   ├── overseerrMerge.ts
│   │   ├── permissions.ts
│   │   ├── refreshToken.ts
│   │   ├── scanners/
│   │   │   ├── baseScanner.ts
│   │   │   ├── jellyfin/
│   │   │   │   └── index.ts
│   │   │   ├── plex/
│   │   │   │   └── index.ts
│   │   │   ├── radarr/
│   │   │   │   └── index.ts
│   │   │   └── sonarr/
│   │   │       └── index.ts
│   │   ├── search.ts
│   │   ├── settings/
│   │   │   ├── index.ts
│   │   │   ├── migrations/
│   │   │   │   ├── 0001_migrate_hostname.ts
│   │   │   │   ├── 0002_migrate_apitokens.ts
│   │   │   │   ├── 0003_emby_media_server_type.ts
│   │   │   │   ├── 0004_migrate_region_setting.ts
│   │   │   │   ├── 0005_migrate_network_settings.ts
│   │   │   │   ├── 0006_remove_lunasea.ts
│   │   │   │   ├── 0007_migrate_arr_tags.ts
│   │   │   │   └── 0008_migrate_blacklist_to_blocklist.ts
│   │   │   └── migrator.ts
│   │   └── watchlistsync.ts
│   ├── logger.ts
│   ├── middleware/
│   │   ├── auth.ts
│   │   ├── clearcookies.ts
│   │   └── deprecation.ts
│   ├── migration/
│   │   ├── postgres/
│   │   │   ├── 1734786061496-InitialMigration.ts
│   │   │   ├── 1734786596045-AddTelegramMessageThreadId.ts
│   │   │   ├── 1734805738349-AddOverrideRules.ts
│   │   │   ├── 1734809898562-FixNullFields.ts
│   │   │   ├── 1737320080282-AddBlacklistTagsColumn.ts
│   │   │   ├── 1743023615532-UpdateWebPush.ts
│   │   │   ├── 1743107707465-AddUserAvatarCacheFields.ts
│   │   │   ├── 1745492376568-UpdateWebPush.ts
│   │   │   ├── 1746811308203-FixIssueTimestamps.ts
│   │   │   ├── 1765233385034-AddUniqueConstraintToPushSubscription.ts
│   │   │   ├── 1770627987304-AddPerformanceIndexes.ts
│   │   │   ├── 1771080196816-RenameBlacklistToBlocklist.ts
│   │   │   ├── 1771259406751-AddForeignKeyIndexes.ts
│   │   │   ├── 1771337333450-RecoveryLinkExpirationDateTime.ts
│   │   │   ├── 1772000000000-FixBlocklistIdDefault.ts
│   │   │   └── 1772048000333-AddMediaTypeToUniqueConstraints.ts
│   │   └── sqlite/
│   │       ├── 1603944374840-InitialMigration.ts
│   │       ├── 1605085519544-SeasonStatus.ts
│   │       ├── 1606730060700-CascadeMigration.ts
│   │       ├── 1607928251245-DropImdbIdConstraint.ts
│   │       ├── 1608217312474-AddUserRequestDeleteCascades.ts
│   │       ├── 1608477467935-AddLastSeasonChangeMedia.ts
│   │       ├── 1608477467936-ForceDropImdbUniqueConstraint.ts
│   │       ├── 1609236552057-RemoveTmdbIdUniqueConstraint.ts
│   │       ├── 1610070934506-LocalUsers.ts
│   │       ├── 1610370640747-Add4kStatusFields.ts
│   │       ├── 1610522845513-AddMediaAddedFieldToMedia.ts
│   │       ├── 1611508672722-AddDisplayNameToUser.ts
│   │       ├── 1611757511674-SonarrRadarrSyncServiceFields.ts
│   │       ├── 1611801511397-AddRatingKeysToMedia.ts
│   │       ├── 1612482778137-AddResetPasswordGuidAndExpiryDate.ts
│   │       ├── 1612571545781-AddLanguageProfileId.ts
│   │       ├── 1613379909641-AddJellyfinUserParams.ts
│   │       ├── 1613412948344-ServerTypeEnum.ts
│   │       ├── 1613615266968-CreateUserSettings.ts
│   │       ├── 1613670041760-AddJellyfinDeviceId.ts
│   │       ├── 1613955393450-UpdateUserSettingsRegions.ts
│   │       ├── 1614334195680-AddTelegramSettingsToUserSettings.ts
│   │       ├── 1615333940450-AddPGPToUserSettings.ts
│   │       ├── 1616576677254-AddUserQuotaFields.ts
│   │       ├── 1617624225464-CreateTagsFieldonMediaRequest.ts
│   │       ├── 1617730837489-AddUserSettingsNotificationAgentsField.ts
│   │       ├── 1618912653565-CreateUserPushSubscriptions.ts
│   │       ├── 1619239659754-AddUserSettingsLocale.ts
│   │       ├── 1619339817343-AddUserSettingsNotificationTypes.ts
│   │       ├── 1634904083966-AddIssues.ts
│   │       ├── 1635079863457-AddPushbulletPushoverUserSettings.ts
│   │       ├── 1660632269368-AddWatchlistSyncUserSetting.ts
│   │       ├── 1660714479373-AddMediaRequestIsAutoRequestedField.ts
│   │       ├── 1672041273674-AddDiscoverSlider.ts
│   │       ├── 1682608634546-AddWatchlists.ts
│   │       ├── 1697393491630-AddUserPushoverSound.ts
│   │       ├── 1699901142442-AddBlacklist.ts
│   │       ├── 1727907530757-AddUserSettingsStreamingRegion.ts
│   │       ├── 1734287582736-AddTelegramMessageThreadId.ts
│   │       ├── 1734805733535-AddOverrideRules.ts
│   │       ├── 1737320080282-AddBlacklistTagsColumn.ts
│   │       ├── 1743023610704-UpdateWebPush.ts
│   │       ├── 1743107645301-AddUserAvatarCacheFields.ts
│   │       ├── 1745492372230-UpdateWebPush.ts
│   │       ├── 1765233385034-AddUniqueConstraintToPushSubscription.ts
│   │       ├── 1770627968781-AddPerformanceIndexes.ts
│   │       ├── 1771080196816-RenameBlacklistToBlocklist.ts
│   │       ├── 1771259394105-AddForeignKeyIndexes.ts
│   │       ├── 1771337037917-RecoveryLinkExpirationDateTime.ts
│   │       └── 1772047972752-AddMediaTypeToUniqueConstraints.ts
│   ├── models/
│   │   ├── Collection.ts
│   │   ├── Movie.ts
│   │   ├── Person.ts
│   │   ├── Search.ts
│   │   ├── Tv.ts
│   │   └── common.ts
│   ├── repositories/
│   │   └── watchlist.repository.ts
│   ├── routes/
│   │   ├── auth.test.ts
│   │   ├── auth.ts
│   │   ├── avatarproxy.ts
│   │   ├── blocklist.ts
│   │   ├── collection.ts
│   │   ├── discover.ts
│   │   ├── imageproxy.ts
│   │   ├── index.ts
│   │   ├── issue.ts
│   │   ├── issueComment.ts
│   │   ├── media.ts
│   │   ├── movie.ts
│   │   ├── overrideRule.ts
│   │   ├── person.ts
│   │   ├── request.ts
│   │   ├── search.ts
│   │   ├── service.ts
│   │   ├── settings/
│   │   │   ├── discover.ts
│   │   │   ├── index.ts
│   │   │   ├── metadata.ts
│   │   │   ├── notifications.ts
│   │   │   ├── radarr.ts
│   │   │   └── sonarr.ts
│   │   ├── tv.ts
│   │   ├── user/
│   │   │   ├── index.ts
│   │   │   └── usersettings.ts
│   │   └── watchlist.ts
│   ├── scripts/
│   │   └── prepareTestDb.ts
│   ├── subscriber/
│   │   ├── IssueCommentSubscriber.ts
│   │   ├── IssueSubscriber.ts
│   │   ├── MediaRequestSubscriber.ts
│   │   └── MediaSubscriber.ts
│   ├── templates/
│   │   └── email/
│   │       ├── generatedpassword/
│   │       │   ├── html.pug
│   │       │   └── subject.pug
│   │       ├── media-issue/
│   │       │   ├── html.pug
│   │       │   └── subject.pug
│   │       ├── media-request/
│   │       │   ├── html.pug
│   │       │   └── subject.pug
│   │       ├── resetpassword/
│   │       │   ├── html.pug
│   │       │   └── subject.pug
│   │       └── test-email/
│   │           ├── html.pug
│   │           └── subject.pug
│   ├── test/
│   │   ├── db.ts
│   │   ├── index.mts
│   │   └── setup.ts
│   ├── tsconfig.json
│   ├── types/
│   │   ├── custom.d.ts
│   │   ├── error.ts
│   │   ├── express-session.d.ts
│   │   ├── express.d.ts
│   │   └── languages.d.ts
│   └── utils/
│       ├── DbColumnHelper.ts
│       ├── appDataVolume.ts
│       ├── appVersion.ts
│       ├── asyncLock.ts
│       ├── customProxyAgent.ts
│       ├── dateHelpers.ts
│       ├── dnsCache.ts
│       ├── getHostname.ts
│       ├── jellyfin.ts
│       ├── profileMiddleware.ts
│       ├── restartFlag.ts
│       ├── seedTestDb.ts
│       └── typeHelpers.ts
├── src/
│   ├── components/
│   │   ├── AirDateBadge/
│   │   │   └── index.tsx
│   │   ├── AppDataWarning/
│   │   │   └── index.tsx
│   │   ├── Blocklist/
│   │   │   └── index.tsx
│   │   ├── BlocklistBlock/
│   │   │   └── index.tsx
│   │   ├── BlocklistModal/
│   │   │   └── index.tsx
│   │   ├── BlocklistedTagsBadge/
│   │   │   └── index.tsx
│   │   ├── BlocklistedTagsSelector/
│   │   │   └── index.tsx
│   │   ├── CollectionDetails/
│   │   │   └── index.tsx
│   │   ├── Common/
│   │   │   ├── Accordion/
│   │   │   │   └── index.tsx
│   │   │   ├── Alert/
│   │   │   │   └── index.tsx
│   │   │   ├── Badge/
│   │   │   │   └── index.tsx
│   │   │   ├── Button/
│   │   │   │   └── index.tsx
│   │   │   ├── ButtonWithDropdown/
│   │   │   │   └── index.tsx
│   │   │   ├── CachedImage/
│   │   │   │   └── index.tsx
│   │   │   ├── ConfirmButton/
│   │   │   │   └── index.tsx
│   │   │   ├── Dropdown/
│   │   │   │   └── index.tsx
│   │   │   ├── Header/
│   │   │   │   └── index.tsx
│   │   │   ├── ImageFader/
│   │   │   │   └── index.tsx
│   │   │   ├── LabeledCheckbox/
│   │   │   │   └── index.tsx
│   │   │   ├── List/
│   │   │   │   └── index.tsx
│   │   │   ├── ListView/
│   │   │   │   └── index.tsx
│   │   │   ├── LoadingSpinner/
│   │   │   │   └── index.tsx
│   │   │   ├── Modal/
│   │   │   │   └── index.tsx
│   │   │   ├── MultiRangeSlider/
│   │   │   │   └── index.tsx
│   │   │   ├── PageTitle/
│   │   │   │   └── index.tsx
│   │   │   ├── PlayButton/
│   │   │   │   └── index.tsx
│   │   │   ├── ProgressCircle/
│   │   │   │   └── index.tsx
│   │   │   ├── SensitiveInput/
│   │   │   │   └── index.tsx
│   │   │   ├── SettingsTabs/
│   │   │   │   └── index.tsx
│   │   │   ├── SlideCheckbox/
│   │   │   │   └── index.tsx
│   │   │   ├── SlideOver/
│   │   │   │   └── index.tsx
│   │   │   ├── StatusBadgeMini/
│   │   │   │   └── index.tsx
│   │   │   ├── Table/
│   │   │   │   └── index.tsx
│   │   │   ├── Tag/
│   │   │   │   └── index.tsx
│   │   │   └── Tooltip/
│   │   │       └── index.tsx
│   │   ├── CompanyCard/
│   │   │   └── index.tsx
│   │   ├── CompanyTag/
│   │   │   └── index.tsx
│   │   ├── Discover/
│   │   │   ├── CreateSlider/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverMovieGenre/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverMovieKeyword/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverMovieLanguage/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverMovies/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverNetwork/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverSliderEdit/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverStudio/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverTv/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverTvGenre/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverTvKeyword/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverTvLanguage/
│   │   │   │   └── index.tsx
│   │   │   ├── DiscoverTvUpcoming.tsx
│   │   │   ├── DiscoverWatchlist/
│   │   │   │   └── index.tsx
│   │   │   ├── FilterSlideover/
│   │   │   │   └── index.tsx
│   │   │   ├── MovieGenreList/
│   │   │   │   └── index.tsx
│   │   │   ├── MovieGenreSlider/
│   │   │   │   └── index.tsx
│   │   │   ├── NetworkSlider/
│   │   │   │   └── index.tsx
│   │   │   ├── PlexWatchlistSlider/
│   │   │   │   └── index.tsx
│   │   │   ├── RecentRequestsSlider/
│   │   │   │   └── index.tsx
│   │   │   ├── RecentlyAddedSlider/
│   │   │   │   └── index.tsx
│   │   │   ├── StudioSlider/
│   │   │   │   └── index.tsx
│   │   │   ├── Trending.tsx
│   │   │   ├── TvGenreList/
│   │   │   │   └── index.tsx
│   │   │   ├── TvGenreSlider/
│   │   │   │   └── index.tsx
│   │   │   ├── Upcoming.tsx
│   │   │   ├── constants.ts
│   │   │   └── index.tsx
│   │   ├── DownloadBlock/
│   │   │   └── index.tsx
│   │   ├── ExternalLinkBlock/
│   │   │   └── index.tsx
│   │   ├── GenreCard/
│   │   │   └── index.tsx
│   │   ├── GenreTag/
│   │   │   └── index.tsx
│   │   ├── IssueBlock/
│   │   │   └── index.tsx
│   │   ├── IssueDetails/
│   │   │   ├── IssueComment/
│   │   │   │   └── index.tsx
│   │   │   ├── IssueDescription/
│   │   │   │   └── index.tsx
│   │   │   └── index.tsx
│   │   ├── IssueList/
│   │   │   ├── IssueItem/
│   │   │   │   └── index.tsx
│   │   │   └── index.tsx
│   │   ├── IssueModal/
│   │   │   ├── CreateIssueModal/
│   │   │   │   └── index.tsx
│   │   │   ├── constants.ts
│   │   │   └── index.tsx
│   │   ├── JSONEditor/
│   │   │   └── index.tsx
│   │   ├── KeywordTag/
│   │   │   └── index.tsx
│   │   ├── LanguageSelector/
│   │   │   └── index.tsx
│   │   ├── Layout/
│   │   │   ├── LanguagePicker/
│   │   │   │   └── index.tsx
│   │   │   ├── MobileMenu/
│   │   │   │   └── index.tsx
│   │   │   ├── Notifications/
│   │   │   │   └── index.tsx
│   │   │   ├── PullToRefresh/
│   │   │   │   └── index.tsx
│   │   │   ├── SearchInput/
│   │   │   │   └── index.tsx
│   │   │   ├── Sidebar/
│   │   │   │   └── index.tsx
│   │   │   ├── UserDropdown/
│   │   │   │   ├── MiniQuotaDisplay/
│   │   │   │   │   └── index.tsx
│   │   │   │   └── index.tsx
│   │   │   ├── UserWarnings/
│   │   │   │   └── index.tsx
│   │   │   ├── VersionStatus/
│   │   │   │   └── index.tsx
│   │   │   └── index.tsx
│   │   ├── LoadingBar/
│   │   │   └── index.tsx
│   │   ├── Login/
│   │   │   ├── AddEmailModal.tsx
│   │   │   ├── JellyfinLogin.tsx
│   │   │   ├── LocalLogin.tsx
│   │   │   ├── PlexLoginButton.tsx
│   │   │   └── index.tsx
│   │   ├── ManageSlideOver/
│   │   │   └── index.tsx
│   │   ├── MediaSlider/
│   │   │   ├── ShowMoreCard/
│   │   │   │   └── index.tsx
│   │   │   └── index.tsx
│   │   ├── MetadataSelector/
│   │   │   └── index.tsx
│   │   ├── MovieDetails/
│   │   │   ├── MovieCast/
│   │   │   │   └── index.tsx
│   │   │   ├── MovieCrew/
│   │   │   │   └── index.tsx
│   │   │   ├── MovieRecommendations.tsx
│   │   │   ├── MovieSimilar.tsx
│   │   │   └── index.tsx
│   │   ├── NotificationTypeSelector/
│   │   │   ├── NotificationType/
│   │   │   │   └── index.tsx
│   │   │   └── index.tsx
│   │   ├── PWAHeader/
│   │   │   └── index.tsx
│   │   ├── PermissionEdit/
│   │   │   └── index.tsx
│   │   ├── PermissionOption/
│   │   │   └── index.tsx
│   │   ├── PersonCard/
│   │   │   └── index.tsx
│   │   ├── PersonDetails/
│   │   │   └── index.tsx
│   │   ├── QuotaSelector/
│   │   │   └── index.tsx
│   │   ├── RegionSelector/
│   │   │   └── index.tsx
│   │   ├── RequestBlock/
│   │   │   └── index.tsx
│   │   ├── RequestButton/
│   │   │   └── index.tsx
│   │   ├── RequestCard/
│   │   │   └── index.tsx
│   │   ├── RequestList/
│   │   │   ├── RequestItem/
│   │   │   │   └── index.tsx
│   │   │   └── index.tsx
│   │   ├── RequestModal/
│   │   │   ├── AdvancedRequester/
│   │   │   │   └── index.tsx
│   │   │   ├── CollectionRequestModal.tsx
│   │   │   ├── MovieRequestModal.tsx
│   │   │   ├── QuotaDisplay/
│   │   │   │   └── index.tsx
│   │   │   ├── SearchByNameModal/
│   │   │   │   └── index.tsx
│   │   │   ├── TvRequestModal.tsx
│   │   │   └── index.tsx
│   │   ├── ResetPassword/
│   │   │   ├── RequestResetLink.tsx
│   │   │   └── index.tsx
│   │   ├── Search/
│   │   │   └── index.tsx
│   │   ├── Selector/
│   │   │   ├── CertificationSelector.tsx
│   │   │   ├── USCertificationSelector.tsx
│   │   │   └── index.tsx
│   │   ├── ServiceWorkerSetup/
│   │   │   └── index.tsx
│   │   ├── Settings/
│   │   │   ├── CopyButton.tsx
│   │   │   ├── LibraryItem.tsx
│   │   │   ├── Notifications/
│   │   │   │   ├── NotificationsDiscord.tsx
│   │   │   │   ├── NotificationsEmail.tsx
│   │   │   │   ├── NotificationsGotify/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── NotificationsNtfy/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── NotificationsPushbullet/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── NotificationsPushover/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── NotificationsSlack/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── NotificationsTelegram.tsx
│   │   │   │   ├── NotificationsWebPush/
│   │   │   │   │   └── index.tsx
│   │   │   │   └── NotificationsWebhook/
│   │   │   │       └── index.tsx
│   │   │   ├── OverrideRule/
│   │   │   │   ├── OverrideRuleModal.tsx
│   │   │   │   └── OverrideRuleTiles.tsx
│   │   │   ├── RadarrModal/
│   │   │   │   └── index.tsx
│   │   │   ├── SettingsAbout/
│   │   │   │   ├── Releases/
│   │   │   │   │   └── index.tsx
│   │   │   │   └── index.tsx
│   │   │   ├── SettingsBadge.tsx
│   │   │   ├── SettingsJellyfin.tsx
│   │   │   ├── SettingsJobsCache/
│   │   │   │   └── index.tsx
│   │   │   ├── SettingsLayout.tsx
│   │   │   ├── SettingsLogs/
│   │   │   │   └── index.tsx
│   │   │   ├── SettingsMain/
│   │   │   │   └── index.tsx
│   │   │   ├── SettingsMetadata.tsx
│   │   │   ├── SettingsNetwork/
│   │   │   │   └── index.tsx
│   │   │   ├── SettingsNotifications.tsx
│   │   │   ├── SettingsPlex.tsx
│   │   │   ├── SettingsServices.tsx
│   │   │   ├── SettingsUsers/
│   │   │   │   └── index.tsx
│   │   │   └── SonarrModal/
│   │   │       └── index.tsx
│   │   ├── Setup/
│   │   │   ├── JellyfinSetup.tsx
│   │   │   ├── LoginWithPlex.tsx
│   │   │   ├── SetupLogin.tsx
│   │   │   ├── SetupSteps.tsx
│   │   │   └── index.tsx
│   │   ├── Slider/
│   │   │   └── index.tsx
│   │   ├── StatusBadge/
│   │   │   └── index.tsx
│   │   ├── StatusChecker/
│   │   │   └── index.tsx
│   │   ├── TitleCard/
│   │   │   ├── ErrorCard.tsx
│   │   │   ├── Placeholder.tsx
│   │   │   ├── TmdbTitleCard.tsx
│   │   │   └── index.tsx
│   │   ├── Toast/
│   │   │   └── index.tsx
│   │   ├── ToastContainer/
│   │   │   └── index.tsx
│   │   ├── TvDetails/
│   │   │   ├── Season/
│   │   │   │   └── index.tsx
│   │   │   ├── TvCast/
│   │   │   │   └── index.tsx
│   │   │   ├── TvCrew/
│   │   │   │   └── index.tsx
│   │   │   ├── TvRecommendations.tsx
│   │   │   ├── TvSimilar.tsx
│   │   │   └── index.tsx
│   │   ├── UserList/
│   │   │   ├── BulkEditModal.tsx
│   │   │   ├── JellyfinImportModal.tsx
│   │   │   ├── PlexImportModal.tsx
│   │   │   └── index.tsx
│   │   └── UserProfile/
│   │       ├── ProfileHeader/
│   │       │   └── index.tsx
│   │       ├── UserSettings/
│   │       │   ├── UserGeneralSettings/
│   │       │   │   └── index.tsx
│   │       │   ├── UserLinkedAccountsSettings/
│   │       │   │   ├── LinkJellyfinModal.tsx
│   │       │   │   └── index.tsx
│   │       │   ├── UserNotificationSettings/
│   │       │   │   ├── UserNotificationsDiscord.tsx
│   │       │   │   ├── UserNotificationsEmail.tsx
│   │       │   │   ├── UserNotificationsPushbullet.tsx
│   │       │   │   ├── UserNotificationsPushover.tsx
│   │       │   │   ├── UserNotificationsTelegram.tsx
│   │       │   │   ├── UserNotificationsWebPush/
│   │       │   │   │   ├── DeviceItem.tsx
│   │       │   │   │   └── index.tsx
│   │       │   │   └── index.tsx
│   │       │   ├── UserPasswordChange/
│   │       │   │   └── index.tsx
│   │       │   ├── UserPermissions/
│   │       │   │   └── index.tsx
│   │       │   └── index.tsx
│   │       └── index.tsx
│   ├── context/
│   │   ├── InteractionContext.tsx
│   │   ├── LanguageContext.tsx
│   │   ├── SettingsContext.tsx
│   │   └── UserContext.tsx
│   ├── hooks/
│   │   ├── useClickOutside.ts
│   │   ├── useDebouncedState.ts
│   │   ├── useDeepLinks.ts
│   │   ├── useDiscover.ts
│   │   ├── useInteraction.ts
│   │   ├── useIsTouch.ts
│   │   ├── useLocale.ts
│   │   ├── useLockBodyScroll.ts
│   │   ├── usePlexLogin.ts
│   │   ├── useRequestOverride.ts
│   │   ├── useRouteGuard.ts
│   │   ├── useSearchInput.ts
│   │   ├── useSettings.ts
│   │   ├── useUpdateQueryParams.ts
│   │   ├── useUser.ts
│   │   └── useVerticalScroll.ts
│   ├── i18n/
│   │   ├── extractMessages.ts
│   │   ├── globalMessages.ts
│   │   └── locale/
│   │       ├── ar.json
│   │       ├── bg.json
│   │       ├── ca.json
│   │       ├── cs.json
│   │       ├── da.json
│   │       ├── de.json
│   │       ├── el.json
│   │       ├── en.json
│   │       ├── es.json
│   │       ├── es_MX.json
│   │       ├── et.json
│   │       ├── eu.json
│   │       ├── fi.json
│   │       ├── fr.json
│   │       ├── he.json
│   │       ├── hi.json
│   │       ├── hr.json
│   │       ├── hu.json
│   │       ├── it.json
│   │       ├── ja.json
│   │       ├── ko.json
│   │       ├── lb.json
│   │       ├── lt.json
│   │       ├── nb_NO.json
│   │       ├── nl.json
│   │       ├── pl.json
│   │       ├── pt_BR.json
│   │       ├── pt_PT.json
│   │       ├── ro.json
│   │       ├── ru.json
│   │       ├── sk.json
│   │       ├── sl.json
│   │       ├── sq.json
│   │       ├── sr.json
│   │       ├── sv.json
│   │       ├── tr.json
│   │       ├── uk.json
│   │       ├── vi.json
│   │       ├── zh_Hans.json
│   │       └── zh_Hant.json
│   ├── pages/
│   │   ├── 404.tsx
│   │   ├── _app.tsx
│   │   ├── _document.tsx
│   │   ├── _error.tsx
│   │   ├── blocklist/
│   │   │   └── index.tsx
│   │   ├── collection/
│   │   │   └── [collectionId]/
│   │   │       └── index.tsx
│   │   ├── discover/
│   │   │   ├── movies/
│   │   │   │   ├── genre/
│   │   │   │   │   └── [genreId]/
│   │   │   │   │       └── index.tsx
│   │   │   │   ├── genres.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   ├── keyword/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── language/
│   │   │   │   │   └── [language]/
│   │   │   │   │       └── index.tsx
│   │   │   │   ├── studio/
│   │   │   │   │   └── [studioId]/
│   │   │   │   │       └── index.tsx
│   │   │   │   └── upcoming.tsx
│   │   │   ├── trending.tsx
│   │   │   ├── tv/
│   │   │   │   ├── genre/
│   │   │   │   │   └── [genreId]/
│   │   │   │   │       └── index.tsx
│   │   │   │   ├── genres.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   ├── keyword/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── language/
│   │   │   │   │   └── [language]/
│   │   │   │   │       └── index.tsx
│   │   │   │   ├── network/
│   │   │   │   │   └── [networkId]/
│   │   │   │   │       └── index.tsx
│   │   │   │   └── upcoming.tsx
│   │   │   └── watchlist.tsx
│   │   ├── index.tsx
│   │   ├── issues/
│   │   │   ├── [issueId]/
│   │   │   │   └── index.tsx
│   │   │   └── index.tsx
│   │   ├── login/
│   │   │   ├── index.tsx
│   │   │   └── plex/
│   │   │       └── loading.tsx
│   │   ├── movie/
│   │   │   └── [movieId]/
│   │   │       ├── cast.tsx
│   │   │       ├── crew.tsx
│   │   │       ├── index.tsx
│   │   │       ├── recommendations.tsx
│   │   │       └── similar.tsx
│   │   ├── person/
│   │   │   └── [personId]/
│   │   │       └── index.tsx
│   │   ├── profile/
│   │   │   ├── index.tsx
│   │   │   ├── settings/
│   │   │   │   ├── index.tsx
│   │   │   │   ├── linked-accounts.tsx
│   │   │   │   ├── main.tsx
│   │   │   │   ├── notifications/
│   │   │   │   │   ├── discord.tsx
│   │   │   │   │   ├── email.tsx
│   │   │   │   │   ├── pushbullet.tsx
│   │   │   │   │   ├── pushover.tsx
│   │   │   │   │   ├── telegram.tsx
│   │   │   │   │   └── webpush.tsx
│   │   │   │   ├── password.tsx
│   │   │   │   └── permissions.tsx
│   │   │   └── watchlist.tsx
│   │   ├── requests/
│   │   │   └── index.tsx
│   │   ├── resetpassword/
│   │   │   ├── [guid]/
│   │   │   │   └── index.tsx
│   │   │   └── index.tsx
│   │   ├── search.tsx
│   │   ├── settings/
│   │   │   ├── about.tsx
│   │   │   ├── index.tsx
│   │   │   ├── jellyfin.tsx
│   │   │   ├── jobs.tsx
│   │   │   ├── logs.tsx
│   │   │   ├── main.tsx
│   │   │   ├── metadata.tsx
│   │   │   ├── network.tsx
│   │   │   ├── notifications/
│   │   │   │   ├── discord.tsx
│   │   │   │   ├── email.tsx
│   │   │   │   ├── gotify.tsx
│   │   │   │   ├── ntfy.tsx
│   │   │   │   ├── pushbullet.tsx
│   │   │   │   ├── pushover.tsx
│   │   │   │   ├── slack.tsx
│   │   │   │   ├── telegram.tsx
│   │   │   │   ├── webhook.tsx
│   │   │   │   └── webpush.tsx
│   │   │   ├── plex.tsx
│   │   │   ├── services.tsx
│   │   │   └── users.tsx
│   │   ├── setup.tsx
│   │   ├── tv/
│   │   │   └── [tvId]/
│   │   │       ├── cast.tsx
│   │   │       ├── crew.tsx
│   │   │       ├── index.tsx
│   │   │       ├── recommendations.tsx
│   │   │       └── similar.tsx
│   │   └── users/
│   │       ├── [userId]/
│   │       │   ├── index.tsx
│   │       │   ├── requests.tsx
│   │       │   ├── settings/
│   │       │   │   ├── index.tsx
│   │       │   │   ├── linked-accounts.tsx
│   │       │   │   ├── main.tsx
│   │       │   │   ├── notifications/
│   │       │   │   │   ├── discord.tsx
│   │       │   │   │   ├── email.tsx
│   │       │   │   │   ├── pushbullet.tsx
│   │       │   │   │   ├── pushover.tsx
│   │       │   │   │   ├── telegram.tsx
│   │       │   │   │   └── webpush.tsx
│   │       │   │   ├── password.tsx
│   │       │   │   └── permissions.tsx
│   │       │   └── watchlist.tsx
│   │       └── index.tsx
│   ├── styles/
│   │   └── globals.css
│   ├── types/
│   │   ├── custom.d.ts
│   │   └── react-intl-auto.d.ts
│   └── utils/
│       ├── creditHelpers.ts
│       ├── defineMessages.ts
│       ├── jellyfin.ts
│       ├── numberHelpers.ts
│       ├── plex.ts
│       ├── polyfillIntl.ts
│       ├── pushSubscriptionHelpers.ts
│       ├── refreshIntervalHelper.ts
│       ├── typeHelpers.ts
│       └── urlValidationHelper.ts
├── stylelint.config.js
├── tailwind.config.js
└── tsconfig.json
Download .txt
SYMBOL INDEX (1308 symbols across 356 files)

FILE: bin/duplicate-detector/build-index.mjs
  constant MODEL_NAME (line 15) | const MODEL_NAME = process.env.EMBEDDING_MODEL || 'Xenova/all-MiniLM-L6-...
  constant OUTPUT_PATH (line 16) | const OUTPUT_PATH = 'issue_index.json';
  constant INCLUDE_CLOSED_DAYS (line 17) | const INCLUDE_CLOSED_DAYS = 90;
  constant MAX_ISSUES (line 18) | const MAX_ISSUES = 5000;
  constant BATCH_SIZE (line 19) | const BATCH_SIZE = 64;
  function main (line 21) | async function main() {

FILE: bin/duplicate-detector/detect.mjs
  constant SIMILARITY_THRESHOLD (line 21) | const SIMILARITY_THRESHOLD = 0.55;
  constant TOP_K (line 22) | const TOP_K = 5;
  constant MAX_COMMENT_CANDIDATES (line 23) | const MAX_COMMENT_CANDIDATES = 3;
  constant MODEL_NAME (line 24) | const MODEL_NAME = process.env.EMBEDDING_MODEL || 'Xenova/all-MiniLM-L6-...
  constant GROQ_MODEL (line 25) | const GROQ_MODEL = process.env.GROQ_MODEL || 'llama-3.3-70b-versatile';
  constant INDEX_PATH (line 26) | const INDEX_PATH = 'issue_index.json';
  constant LABEL_NAME (line 27) | const LABEL_NAME = 'possible-duplicate';
  constant GROQ_API_KEY (line 29) | const GROQ_API_KEY = process.env.GROQ_API_KEY || '';
  constant ISSUE_NUMBER (line 30) | const ISSUE_NUMBER = parseInt(process.env.ISSUE_NUMBER, 10);
  function loadIndex (line 32) | function loadIndex(path) {
  function findSimilar (line 45) | function findSimilar(
  constant CONFIRM_SYSTEM_PROMPT (line 67) | const CONFIRM_SYSTEM_PROMPT = `You are a GitHub issue triage assistant. ...
  function confirmWithLlm (line 81) | async function confirmWithLlm(newIssue, candidates) {
  function formatComment (line 170) | function formatComment(candidates) {
  function main (line 197) | async function main() {

FILE: bin/duplicate-detector/utils.mjs
  constant GITHUB_API (line 1) | const GITHUB_API = 'https://api.github.com';
  constant GITHUB_TOKEN (line 2) | const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
  constant GITHUB_REPOSITORY (line 3) | const GITHUB_REPOSITORY = process.env.GITHUB_REPOSITORY;
  function ghHeaders (line 5) | function ghHeaders() {
  function fetchIssues (line 12) | async function fetchIssues({
  function getIssue (line 54) | async function getIssue(issueNumber) {
  function postComment (line 65) | async function postComment(issueNumber, body) {
  function addLabel (line 82) | async function addLabel(issueNumber, label) {
  function issueText (line 104) | function issueText(title, body) {
  function dotProduct (line 110) | function dotProduct(a, b) {

FILE: cypress/support/commands.ts
  method validate (line 21) | validate() {

FILE: cypress/support/index.ts
  type Chainable (line 6) | interface Chainable {

FILE: gen-docs/src/components/SeerrVersion/index.tsx
  function fetchVersion (line 7) | async function fetchVersion() {

FILE: next.config.js
  method webpack (line 16) | webpack(config) {

FILE: public/sw.js
  constant OFFLINE_VERSION (line 6) | const OFFLINE_VERSION = 5;
  constant CACHE_NAME (line 7) | const CACHE_NAME = 'offline';
  constant OFFLINE_URL (line 9) | const OFFLINE_URL = '/offline.html';

FILE: server/api/animelist.ts
  constant UPDATE_INTERVAL_MSEC (line 7) | const UPDATE_INTERVAL_MSEC = 24 * 3600 * 1000;
  constant MAPPING_URL (line 9) | const MAPPING_URL =
  constant LOCAL_PATH (line 11) | const LOCAL_PATH = process.env.CONFIG_DIRECTORY
  type AnimeMapping (line 20) | interface AnimeMapping {
  type Anime (line 28) | interface Anime {
  type AnimeList (line 41) | interface AnimeList {
  type AnidbItem (line 47) | interface AnidbItem {
  class AnimeListMapping (line 54) | class AnimeListMapping {

FILE: server/api/externalapi.ts
  constant DEFAULT_TTL (line 8) | const DEFAULT_TTL = 300;
  constant DEFAULT_ROLLING_BUFFER (line 11) | const DEFAULT_ROLLING_BUFFER = 10000;
  type ExternalAPIOptions (line 13) | interface ExternalAPIOptions {
  class ExternalAPI (line 23) | class ExternalAPI {
    method constructor (line 28) | constructor(
    method get (line 56) | protected async get<T>(
    method post (line 79) | protected async post<T>(
    method getRolling (line 104) | protected async getRolling<T>(
    method removeCache (line 139) | protected removeCache(endpoint: string, options?: Record<string, unkno...
    method serializeCacheKey (line 146) | private serializeCacheKey(

FILE: server/api/github.ts
  type GitHubRelease (line 5) | interface GitHubRelease {
  type GithubCommit (line 24) | interface GithubCommit {
  class GithubAPI (line 64) | class GithubAPI extends ExternalAPI {
    method constructor (line 65) | constructor() {
    method getSeerrReleases (line 79) | public async getSeerrReleases({
    method getSeerrCommits (line 104) | public async getSeerrCommits({

FILE: server/api/jellyfin.ts
  type JellyfinUserResponse (line 11) | interface JellyfinUserResponse {
  type JellyfinDevice (line 25) | interface JellyfinDevice {
  type JellyfinDevicesResponse (line 36) | interface JellyfinDevicesResponse {
  type JellyfinLoginResponse (line 42) | interface JellyfinLoginResponse {
  type JellyfinUserListResponse (line 47) | interface JellyfinUserListResponse {
  type JellyfinMediaFolder (line 51) | interface JellyfinMediaFolder {
  type JellyfinLibrary (line 58) | interface JellyfinLibrary {
  type JellyfinLibraryItem (line 65) | interface JellyfinLibraryItem {
  type JellyfinMediaStream (line 81) | interface JellyfinMediaStream {
  type JellyfinMediaSource (line 92) | interface JellyfinMediaSource {
  type JellyfinLibraryItemExtended (line 101) | interface JellyfinLibraryItemExtended extends JellyfinLibraryItem {
  type EpisodeReturn (line 116) | type EpisodeReturn<T> = T extends { includeMediaInfo: true }
  type JellyfinItemsReponse (line 120) | interface JellyfinItemsReponse {
  class JellyfinAPI (line 126) | class JellyfinAPI extends ExternalAPI {
    method constructor (line 130) | constructor(
    method login (line 163) | public async login(
    method setUserId (line 220) | public setUserId(userId: string): void {
    method getSystemInfo (line 225) | public async getSystemInfo(): Promise<any> {
    method getServerName (line 235) | public async getServerName(): Promise<string> {
    method getUsers (line 252) | public async getUsers(): Promise<JellyfinUserListResponse> {
    method getUser (line 267) | public async getUser(): Promise<JellyfinUserResponse> {
    method getLibraries (line 283) | public async getLibraries(): Promise<JellyfinLibrary[]> {
    method mapLibraries (line 311) | private mapLibraries(mediaFolders: JellyfinMediaFolder[]): JellyfinLib...
    method getLibraryContents (line 337) | public async getLibraryContents(id: string): Promise<JellyfinLibraryIt...
    method getRecentlyAdded (line 356) | public async getRecentlyAdded(id: string): Promise<JellyfinLibraryItem...
    method getItemData (line 381) | public async getItemData(
    method getSeasons (line 408) | public async getSeasons(seriesID: string): Promise<JellyfinLibraryItem...
    method getEpisodes (line 423) | public async getEpisodes<
    method createApiToken (line 454) | public async createApiToken(appName: string): Promise<string> {

FILE: server/api/plexapi.ts
  type PlexStatusResponse (line 6) | interface PlexStatusResponse {
  type PlexLibraryItem (line 13) | interface PlexLibraryItem {
  type PlexLibraryResponse (line 30) | interface PlexLibraryResponse {
  type PlexLibrary (line 37) | interface PlexLibrary {
  type PlexLibrariesResponse (line 44) | interface PlexLibrariesResponse {
  type PlexMetadata (line 50) | interface PlexMetadata {
  type Media (line 72) | interface Media {
  type PlexMetadataResponse (line 88) | interface PlexMetadataResponse {
  class PlexAPI (line 94) | class PlexAPI extends ExternalAPI {
    method constructor (line 95) | constructor({
    method getStatus (line 126) | public async getStatus(): Promise<PlexStatusResponse> {
    method getLibraries (line 130) | public async getLibraries(): Promise<PlexLibrary[]> {
    method syncLibraries (line 136) | public async syncLibraries(): Promise<void> {
    method getLibraryContents (line 176) | public async getLibraryContents(
    method getMetadata (line 196) | public async getMetadata(
    method getChildrenMetadata (line 209) | public async getChildrenMetadata(key: string): Promise<PlexMetadata[]> {
    method getRecentlyAdded (line 217) | public async getRecentlyAdded(

FILE: server/api/plextv.ts
  type PlexAccountResponse (line 9) | interface PlexAccountResponse {
  type PlexUser (line 13) | interface PlexUser {
  type ConnectionResponse (line 35) | interface ConnectionResponse {
  type DeviceResponse (line 45) | interface DeviceResponse {
  type ServerResponse (line 74) | interface ServerResponse {
  type UsersResponse (line 86) | interface UsersResponse {
  type WatchlistResponse (line 101) | interface WatchlistResponse {
  type MetadataResponse (line 110) | interface MetadataResponse {
  type PlexWatchlistItem (line 123) | interface PlexWatchlistItem {
  type PlexWatchlistCache (line 131) | interface PlexWatchlistCache {
  class PlexTvAPI (line 136) | class PlexTvAPI extends ExternalAPI {
    method constructor (line 139) | constructor(authToken: string) {
    method getDevices (line 156) | public async getDevices(): Promise<PlexDevice[]> {
    method getUser (line 212) | public async getUser(): Promise<PlexUser> {
    method checkUserAccess (line 228) | public async checkUserAccess(userId: number): Promise<boolean> {
    method getUsers (line 257) | public async getUsers(): Promise<UsersResponse> {
    method getWatchlist (line 269) | public async getWatchlist({
    method pingToken (line 383) | public async pingToken() {

FILE: server/api/provider.ts
  type TvShowProvider (line 6) | interface TvShowProvider {

FILE: server/api/pushover.ts
  type PushoverSoundsResponse (line 3) | interface PushoverSoundsResponse {
  type PushoverSound (line 11) | interface PushoverSound {
  class PushoverAPI (line 27) | class PushoverAPI extends ExternalAPI {
    method constructor (line 28) | constructor() {
    method getSounds (line 41) | public async getSounds(appToken: string): Promise<PushoverSound[]> {

FILE: server/api/rating/imdbRadarrProxy.ts
  type IMDBRadarrProxyResponse (line 4) | type IMDBRadarrProxyResponse = IMDBMovie[];
  type IMDBMovie (line 6) | interface IMDBMovie {
  type Rating (line 37) | interface Rating {
  type MovieRatings (line 44) | interface MovieRatings {
  type Tmdb (line 51) | interface Tmdb {
  type Imdb (line 57) | interface Imdb {
  type Metacritic (line 63) | interface Metacritic {
  type RottenTomatoes (line 69) | interface RottenTomatoes {
  type Image (line 75) | interface Image {
  type AlternativeTitle (line 80) | interface AlternativeTitle {
  type Translation (line 86) | interface Translation {
  type Recommendation (line 92) | interface Recommendation {
  type Credits (line 97) | interface Credits {
  type Cast (line 102) | interface Cast {
  type Image2 (line 111) | interface Image2 {
  type Crew (line 116) | interface Crew {
  type Image3 (line 125) | interface Image3 {
  type Certification (line 130) | interface Certification {
  type Collection (line 135) | interface Collection {
  type IMDBRating (line 144) | interface IMDBRating {
  class IMDBRadarrProxy (line 157) | class IMDBRadarrProxy extends ExternalAPI {
    method constructor (line 158) | constructor() {
    method getMovieRatings (line 173) | public async getMovieRatings(IMDBid: string): Promise<IMDBRating | nul...

FILE: server/api/rating/rottentomatoes.ts
  type RTAlgoliaSearchResponse (line 6) | interface RTAlgoliaSearchResponse {
  type RTAlgoliaHit (line 13) | interface RTAlgoliaHit {
  type RTRating (line 41) | interface RTRating {
  constant INEXACT_TITLE_FACTOR (line 52) | const INEXACT_TITLE_FACTOR = 0.25;
  constant ALTERNATE_TITLE_FACTOR (line 53) | const ALTERNATE_TITLE_FACTOR = 0.8;
  constant PER_YEAR_PENALTY (line 54) | const PER_YEAR_PENALTY = 0.4;
  constant MINIMUM_SCORE (line 55) | const MINIMUM_SCORE = 0.175;
  class RottenTomatoes (line 102) | class RottenTomatoes extends ExternalAPI {
    method constructor (line 103) | constructor() {
    method getMovieRatings (line 133) | public async getMovieRatings(
    method getTVRatings (line 176) | public async getTVRatings(

FILE: server/api/ratings.ts
  type RatingResponse (line 4) | interface RatingResponse {

FILE: server/api/servarr/base.ts
  type SystemStatus (line 6) | interface SystemStatus {
  type RootFolder (line 35) | interface RootFolder {
  type QualityProfile (line 46) | interface QualityProfile {
  type QueueItem (line 51) | interface QueueItem {
  type Tag (line 67) | interface Tag {
  type QueueResponse (line 72) | interface QueueResponse<QueueItemAppendT> {
  class ServarrBase (line 81) | class ServarrBase<QueueItemAppendT> extends ExternalAPI {
    method buildUrl (line 82) | static buildUrl(settings: DVRSettings, path?: string): string {
    method constructor (line 90) | constructor({
    method refreshMonitoredDownloads (line 232) | async refreshMonitoredDownloads(): Promise<void> {
    method runCommand (line 236) | protected async runCommand(

FILE: server/api/servarr/radarr.ts
  type RadarrMovieOptions (line 4) | interface RadarrMovieOptions {
  type RadarrMovie (line 17) | interface RadarrMovie {
  class RadarrAPI (line 66) | class RadarrAPI extends ServarrBase<{ movieId: number }> {
    method constructor (line 67) | constructor({ url, apiKey }: { url: string; apiKey: string }) {
    method getMovieByTmdbId (line 95) | public async getMovieByTmdbId(id: number): Promise<RadarrMovie> {
    method searchMovie (line 251) | public async searchMovie(movieId: number): Promise<void> {

FILE: server/api/servarr/sonarr.ts
  type SonarrSeason (line 4) | interface SonarrSeason {
  type EpisodeResult (line 16) | interface EpisodeResult {
  type SonarrSeries (line 32) | interface SonarrSeries {
  type AddSeriesOptions (line 91) | interface AddSeriesOptions {
  type LanguageProfile (line 106) | interface LanguageProfile {
  class SonarrAPI (line 111) | class SonarrAPI extends ServarrBase<{
    method constructor (line 116) | constructor({ url, apiKey }: { url: string; apiKey: string }) {
    method getSeries (line 120) | public async getSeries(): Promise<SonarrSeries[]> {
    method getSeriesById (line 132) | public async getSeriesById(id: number): Promise<SonarrSeries> {
    method getSeriesByTitle (line 145) | public async getSeriesByTitle(title: string): Promise<SonarrSeries[]> {
    method getSeriesByTvdbId (line 168) | public async getSeriesByTvdbId(id: number): Promise<SonarrSeries> {
    method addSeries (line 191) | public async addSeries(options: AddSeriesOptions): Promise<SonarrSerie...
    method getLanguageProfiles (line 315) | public async getLanguageProfiles(): Promise<LanguageProfile[]> {
    method searchSeries (line 337) | public async searchSeries(seriesId: number): Promise<void> {
    method getEpisodes (line 357) | public async getEpisodes(seriesId: number): Promise<EpisodeResult[]> {
    method monitorEpisodes (line 373) | public async monitorEpisodes(episodeIds: number[]): Promise<void> {
    method buildSeasonList (line 389) | private buildSeasonList(

FILE: server/api/tautulli.ts
  type TautulliHistoryRecord (line 9) | interface TautulliHistoryRecord {
  type TautulliHistoryResponse (line 50) | interface TautulliHistoryResponse {
  type TautulliWatchStats (line 65) | interface TautulliWatchStats {
  type TautulliWatchStatsResponse (line 71) | interface TautulliWatchStatsResponse {
  type TautulliWatchUser (line 79) | interface TautulliWatchUser {
  type TautulliWatchUsersResponse (line 88) | interface TautulliWatchUsersResponse {
  type TautulliInfo (line 96) | interface TautulliInfo {
  type TautulliInfoResponse (line 109) | interface TautulliInfoResponse {
  class TautulliAPI (line 117) | class TautulliAPI {
    method constructor (line 120) | constructor(settings: TautulliSettings) {
    method getInfo (line 130) | public async getInfo(): Promise<TautulliInfo> {
    method getMediaWatchStats (line 149) | public async getMediaWatchStats(
    method getMediaWatchUsers (line 178) | public async getMediaWatchUsers(
    method getUserWatchStats (line 207) | public async getUserWatchStats(user: User): Promise<TautulliWatchStats> {
    method getUserWatchHistory (line 239) | public async getUserWatchHistory(

FILE: server/api/themoviedb/constants.ts
  constant ANIME_KEYWORD_ID (line 1) | const ANIME_KEYWORD_ID = 210024;

FILE: server/api/themoviedb/index.ts
  type SearchOptions (line 31) | interface SearchOptions {
  type SingleSearchOptions (line 38) | interface SingleSearchOptions extends SearchOptions {
  type SortOptions (line 61) | type SortOptions = (typeof SortOptionsIterable)[number];
  type TmdbCertificationResponse (line 63) | interface TmdbCertificationResponse {
  type DiscoverMovieOptions (line 73) | interface DiscoverMovieOptions {
  type DiscoverTvOptions (line 100) | interface DiscoverTvOptions {
  class TheMovieDb (line 127) | class TheMovieDb extends ExternalAPI implements TvShowProvider {
    method constructor (line 131) | constructor({
    method getMovieRecommendations (line 446) | public async getMovieRecommendations({
    method getMovieSimilar (line 474) | public async getMovieSimilar({
    method getMoviesByKeyword (line 502) | public async getMoviesByKeyword({
    method getTvRecommendations (line 531) | public async getTvRecommendations({
    method getTvSimilar (line 560) | public async getTvSimilar({
    method getByExternalId (line 874) | public async getByExternalId({
    method getMediaByImdbId (line 908) | public async getMediaByImdbId({
    method getShowByTvdbId (line 948) | public async getShowByTvdbId({
    method getCollection (line 979) | public async getCollection({
    method getRegions (line 1004) | public async getRegions(): Promise<TmdbRegion[]> {
    method getLanguages (line 1022) | public async getLanguages(): Promise<TmdbLanguage[]> {
    method getStudio (line 1040) | public async getStudio(studioId: number): Promise<TmdbProductionCompan...
    method getNetwork (line 1054) | public async getNetwork(networkId: number): Promise<TmdbNetwork> {
    method getMovieGenres (line 1066) | public async getMovieGenres({
    method getTvGenres (line 1119) | public async getTvGenres({
    method getKeywordDetails (line 1206) | public async getKeywordDetails({
    method searchKeyword (line 1229) | public async searchKeyword({
    method searchCompany (line 1256) | public async searchCompany({
    method getAvailableWatchProviderRegions (line 1283) | public async getAvailableWatchProviderRegions({
    method getMovieWatchProviders (line 1308) | public async getMovieWatchProviders({
    method getTvWatchProviders (line 1336) | public async getTvWatchProviders({

FILE: server/api/themoviedb/interfaces.ts
  type TmdbMediaResult (line 1) | interface TmdbMediaResult {
  type TmdbMovieResult (line 14) | interface TmdbMovieResult extends TmdbMediaResult {
  type TmdbTvResult (line 23) | interface TmdbTvResult extends TmdbMediaResult {
  type TmdbCollectionResult (line 31) | interface TmdbCollectionResult {
  type TmdbPersonResult (line 43) | interface TmdbPersonResult {
  type TmdbPaginatedResponse (line 53) | interface TmdbPaginatedResponse {
  type TmdbSearchMultiResponse (line 59) | interface TmdbSearchMultiResponse extends TmdbPaginatedResponse {
  type TmdbSearchMovieResponse (line 68) | interface TmdbSearchMovieResponse extends TmdbPaginatedResponse {
  type TmdbSearchTvResponse (line 72) | interface TmdbSearchTvResponse extends TmdbPaginatedResponse {
  type TmdbUpcomingMoviesResponse (line 76) | interface TmdbUpcomingMoviesResponse extends TmdbPaginatedResponse {
  type TmdbExternalIdResponse (line 84) | interface TmdbExternalIdResponse {
  type TmdbCreditCast (line 90) | interface TmdbCreditCast {
  type TmdbAggregateCreditCast (line 101) | interface TmdbAggregateCreditCast extends TmdbCreditCast {
  type TmdbCreditCrew (line 109) | interface TmdbCreditCrew {
  type TmdbExternalIds (line 119) | interface TmdbExternalIds {
  type TmdbProductionCompany (line 130) | interface TmdbProductionCompany {
  type TmdbMovieDetails (line 140) | interface TmdbMovieDetails {
  type TmdbVideo (line 196) | interface TmdbVideo {
  type TmdbTvEpisodeResult (line 212) | interface TmdbTvEpisodeResult {
  type TmdbTvSeasonResult (line 226) | interface TmdbTvSeasonResult {
  type TmdbTvDetails (line 236) | interface TmdbTvDetails {
  type TmdbVideoResult (line 307) | interface TmdbVideoResult {
  type TmdbTvRatingResult (line 311) | interface TmdbTvRatingResult {
  type TmdbRating (line 315) | interface TmdbRating {
  type TmdbMovieReleaseResult (line 320) | interface TmdbMovieReleaseResult {
  type TmdbRelease (line 324) | interface TmdbRelease extends TmdbRating {
  type TmdbKeyword (line 334) | interface TmdbKeyword {
  type TmdbPersonDetails (line 339) | interface TmdbPersonDetails {
  type TmdbPersonCredit (line 356) | interface TmdbPersonCredit {
  type TmdbPersonCreditCast (line 380) | interface TmdbPersonCreditCast extends TmdbPersonCredit {
  type TmdbPersonCreditCrew (line 384) | interface TmdbPersonCreditCrew extends TmdbPersonCredit {
  type TmdbPersonCombinedCredits (line 389) | interface TmdbPersonCombinedCredits {
  type TmdbSeasonWithEpisodes (line 395) | interface TmdbSeasonWithEpisodes extends Omit<
  type TmdbCollection (line 403) | interface TmdbCollection {
  type TmdbRegion (line 412) | interface TmdbRegion {
  type TmdbLanguage (line 417) | interface TmdbLanguage {
  type TmdbGenresResult (line 423) | interface TmdbGenresResult {
  type TmdbGenre (line 427) | interface TmdbGenre {
  type TmdbNetwork (line 432) | interface TmdbNetwork {
  type TmdbWatchProviders (line 441) | interface TmdbWatchProviders {
  type TmdbWatchProviderDetails (line 447) | interface TmdbWatchProviderDetails {
  type TmdbKeywordSearchResponse (line 454) | interface TmdbKeywordSearchResponse extends TmdbPaginatedResponse {
  type TmdbCompany (line 459) | interface TmdbCompany {
  type TmdbCompanySearchResponse (line 465) | interface TmdbCompanySearchResponse extends TmdbPaginatedResponse {
  type TmdbWatchProviderRegion (line 469) | interface TmdbWatchProviderRegion {

FILE: server/api/tvdb/index.ts
  type TvdbConfig (line 21) | interface TvdbConfig {
  constant DEFAULT_CONFIG (line 28) | const DEFAULT_CONFIG: TvdbConfig = {
  type TvdbIdStatus (line 35) | const enum TvdbIdStatus {
  type TvdbId (line 39) | type TvdbId = number;
  type ValidTvdbId (line 40) | type ValidTvdbId = Exclude<TvdbId, TvdbIdStatus.INVALID>;
  class Tvdb (line 42) | class Tvdb extends ExternalAPI implements TvShowProvider {
    method constructor (line 50) | constructor(pin?: string) {
    method getInstance (line 67) | public static async getInstance(): Promise<Tvdb> {
    method refreshToken (line 76) | private async refreshToken(): Promise<void> {
    method test (line 103) | public async test(): Promise<void> {
    method login (line 112) | async login(): Promise<TvdbLoginResponse> {
    method getShowByTvdbId (line 136) | public async getShowByTvdbId({
    method getTvShow (line 168) | public async getTvShow({
    method getTvSeason (line 198) | public async getTvSeason({
    method enrichTmdbShowWithTvdbData (line 237) | private async enrichTmdbShowWithTvdbData(
    method fetchTvdbShowData (line 260) | private async fetchTvdbShowData(tvdbId: number): Promise<TvdbTvDetails> {
    method processSeasons (line 274) | private processSeasons(tvdbData: TvdbTvDetails): TmdbTvSeasonResult[] {
    method createSeasonData (line 290) | private createSeasonData(
    method getTvdbSeasonData (line 322) | private async getTvdbSeasonData(
    method getSeasonWithTranslation (line 380) | private async getSeasonWithTranslation(
    method getSeasonWithOriginalLanguage (line 463) | private async getSeasonWithOriginalLanguage(
    method processEpisodes (line 500) | private processEpisodes(
    method createEpisodeData (line 515) | private createEpisodeData(
    method createEmptySeasonResponse (line 538) | private createEmptySeasonResponse(tvId: number): TmdbSeasonWithEpisodes {
    method getTvdbIdFromTmdb (line 550) | private getTvdbIdFromTmdb(tmdbTvShow: TmdbTvDetails): TvdbId {
    method isValidTvdbId (line 554) | private isValidTvdbId(tvdbId: TvdbId): tvdbId is ValidTvdbId {
    method handleError (line 558) | private handleError(context: string, error: Error): void {

FILE: server/api/tvdb/interfaces.ts
  type TvdbBaseResponse (line 3) | interface TvdbBaseResponse<T> {
  type TvdbPagination (line 9) | interface TvdbPagination {
  type TvdbLoginResponse (line 17) | interface TvdbLoginResponse {
  type TvDetailsAliases (line 21) | interface TvDetailsAliases {
  type TvDetailsStatus (line 26) | interface TvDetailsStatus {
  type TvdbTvDetails (line 33) | interface TvdbTvDetails {
  type TvdbCompanyType (line 56) | interface TvdbCompanyType {
  type TvdbParentCompany (line 61) | interface TvdbParentCompany {
  type TvdbCompany (line 70) | interface TvdbCompany {
  type TvdbType (line 86) | interface TvdbType {
  type TvdbArtwork (line 93) | interface TvdbArtwork {
  type TvdbEpisode (line 105) | interface TvdbEpisode {
  type TvdbSeasonDetails (line 126) | interface TvdbSeasonDetails {
  type TvdbEpisodeTranslation (line 151) | interface TvdbEpisodeTranslation {
  constant TMDB_TO_TVDB_MAPPING (line 157) | const TMDB_TO_TVDB_MAPPING: Record<string, string> & {
  function convertTMDBToTVDB (line 199) | function convertTMDBToTVDB(tmdbCode: string): string | null {
  function convertTmdbLanguageToTvdbWithFallback (line 209) | function convertTmdbLanguageToTvdbWithFallback(

FILE: server/constants/discover.ts
  type DiscoverSliderType (line 3) | enum DiscoverSliderType {

FILE: server/constants/error.ts
  type ApiErrorCode (line 1) | enum ApiErrorCode {

FILE: server/constants/issue.ts
  type IssueType (line 1) | enum IssueType {
  type IssueStatus (line 8) | enum IssueStatus {

FILE: server/constants/media.ts
  type MediaRequestStatus (line 1) | enum MediaRequestStatus {
  type MediaType (line 9) | enum MediaType {
  type MediaStatus (line 14) | enum MediaStatus {

FILE: server/constants/server.ts
  type MediaServerType (line 1) | enum MediaServerType {
  type ServerType (line 8) | enum ServerType {

FILE: server/constants/user.ts
  type UserType (line 1) | enum UserType {

FILE: server/datasource.ts
  constant DB_SSL_PREFIX (line 6) | const DB_SSL_PREFIX = 'DB_SSL_';
  function boolFromEnv (line 8) | function boolFromEnv(envVar: string, defaultVal = false) {
  function stringOrReadFileFromEnv (line 15) | function stringOrReadFileFromEnv(envVar: string): Buffer | string | unde...
  function buildSslConfig (line 26) | function buildSslConfig(): TlsOptions | undefined {
  function getDataSource (line 118) | function getDataSource(): DataSourceOptions {

FILE: server/entity/Blocklist.ts
  class Blocklist (line 22) | class Blocklist implements BlocklistItem {
    method constructor (line 54) | constructor(init?: Partial<Blocklist>) {
    method addToBlocklist (line 58) | public static async addToBlocklist(

FILE: server/entity/DiscoverSlider.ts
  class DiscoverSlider (line 8) | @Entity()
    method bootstrapSliders (line 10) | public static async bootstrapSliders(): Promise<void> {
    method constructor (line 63) | constructor(init?: Partial<DiscoverSlider>) {

FILE: server/entity/Issue.ts
  class Issue (line 17) | @Entity()
    method sortComments (line 74) | sortComments() {
    method constructor (line 78) | constructor(init?: Partial<Issue>) {

FILE: server/entity/IssueComment.ts
  class IssueComment (line 12) | @Entity()
    method constructor (line 43) | constructor(init?: Partial<IssueComment>) {

FILE: server/entity/Media.ts
  class Media (line 28) | @Entity()
    method getRelatedMedia (line 31) | public static async getRelatedMedia(
    method getMedia (line 64) | public static async getMedia(
    method constructor (line 202) | constructor(init?: Partial<Media>) {
    method resetServiceData (line 206) | public resetServiceData(): void {
    method setPlexUrls (line 220) | public setPlexUrls(): void {
    method setServiceUrl (line 273) | public setServiceUrl(): void {
    method getDownloadingItem (line 338) | public getDownloadingItem(): void {

FILE: server/entity/MediaRequest.ts
  class RequestPermissionError (line 34) | class RequestPermissionError extends Error {}
  class QuotaRestrictedError (line 35) | class QuotaRestrictedError extends Error {}
  class DuplicateMediaRequestError (line 36) | class DuplicateMediaRequestError extends Error {}
  class NoSeasonsAvailableError (line 37) | class NoSeasonsAvailableError extends Error {}
  class BlocklistedMediaError (line 38) | class BlocklistedMediaError extends Error {}
  type MediaRequestOptions (line 40) | type MediaRequestOptions = {
  class MediaRequest (line 45) | class MediaRequest {
    method request (line 46) | public static async request(
    method constructor (line 614) | constructor(init?: Partial<MediaRequest>) {
    method notifyNewRequest (line 619) | public async notifyNewRequest(): Promise<void> {
    method notifyApprovedOrDeclined (line 653) | public async notifyApprovedOrDeclined(autoApproved = false): Promise<v...
    method autoapprovalNotification (line 704) | public async autoapprovalNotification(): Promise<void> {
    method sortSeasons (line 711) | private sortSeasons() {
    method sendNotification (line 717) | static async sendNotification(

FILE: server/entity/OverrideRule.ts
  class OverrideRule (line 4) | @Entity()
    method constructor (line 46) | constructor(init?: Partial<OverrideRule>) {

FILE: server/entity/Season.ts
  class Season (line 12) | @Entity()
    method constructor (line 42) | constructor(init?: Partial<Season>) {

FILE: server/entity/SeasonRequest.ts
  class SeasonRequest (line 12) | @Entity()
    method constructor (line 39) | constructor(init?: Partial<SeasonRequest>) {

FILE: server/entity/Session.ts
  class Session (line 5) | class Session implements ISession {

FILE: server/entity/User.ts
  class User (line 34) | class User {
    method filterMany (line 35) | public static filterMany(
    method constructor (line 161) | constructor(init?: Partial<User>) {
    method filter (line 165) | public filter(showFiltered?: boolean): Partial<User> {
    method hasPermission (line 176) | public hasPermission(
    method passwordMatch (line 183) | public passwordMatch(password: string): Promise<boolean> {
    method setPassword (line 193) | public async setPassword(password: string): Promise<void> {
    method generatePassword (line 198) | public async generatePassword(): Promise<void> {
    method resetPassword (line 229) | public async resetPassword(): Promise<void> {
    method setDisplayName (line 268) | public setDisplayName(): void {
    method getQuota (line 273) | public async getQuota(): Promise<QuotaResponse> {

FILE: server/entity/UserPushSubscription.ts
  class UserPushSubscription (line 14) | class UserPushSubscription {
    method constructor (line 44) | constructor(init?: Partial<UserPushSubscription>) {

FILE: server/entity/UserSettings.ts
  constant ALL_NOTIFICATIONS (line 13) | const ALL_NOTIFICATIONS = Object.values(Notification)
  class UserSettings (line 18) | class UserSettings {
    method constructor (line 19) | constructor(init?: Partial<UserSettings>) {
    method hasNotificationType (line 135) | public hasNotificationType(

FILE: server/entity/Watchlist.ts
  class DuplicateWatchlistRequestError (line 19) | class DuplicateWatchlistRequestError extends Error {}
  class NotFoundError (line 20) | class NotFoundError extends Error {
    method constructor (line 21) | constructor(message = 'Not found') {
  class Watchlist (line 29) | class Watchlist implements WatchlistItem {
    method constructor (line 70) | constructor(init?: Partial<Watchlist>) {
    method createWatchlist (line 74) | public static async createWatchlist({
    method deleteWatchlist (line 143) | public static async deleteWatchlist(

FILE: server/index.ts
  constant API_SPEC_PATH (line 47) | const API_SPEC_PATH = path.join(__dirname, '../seerr-api.yml');

FILE: server/interfaces/api/blocklistInterfaces.ts
  type BlocklistItem (line 4) | interface BlocklistItem {
  type BlocklistResultsResponse (line 13) | interface BlocklistResultsResponse extends PaginatedResponse {

FILE: server/interfaces/api/common.ts
  type PageInfo (line 1) | interface PageInfo {
  type PaginatedResponse (line 8) | interface PaginatedResponse {
  type NonFunctionPropertyNames (line 15) | type NonFunctionPropertyNames<T> = {
  type NonFunctionProperties (line 23) | type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;

FILE: server/interfaces/api/discoverInterfaces.ts
  type GenreSliderItem (line 1) | interface GenreSliderItem {
  type WatchlistItem (line 7) | interface WatchlistItem {
  type WatchlistResponse (line 15) | interface WatchlistResponse {

FILE: server/interfaces/api/issueInterfaces.ts
  type IssueResultsResponse (line 4) | interface IssueResultsResponse extends PaginatedResponse {

FILE: server/interfaces/api/mediaInterfaces.ts
  type MediaResultsResponse (line 5) | interface MediaResultsResponse extends PaginatedResponse {
  type MediaWatchDataResponse (line 9) | interface MediaWatchDataResponse {

FILE: server/interfaces/api/overrideRuleInterfaces.ts
  type OverrideRuleResultsResponse (line 3) | type OverrideRuleResultsResponse = OverrideRule[];

FILE: server/interfaces/api/personInterfaces.ts
  type PersonCombinedCreditsResponse (line 3) | interface PersonCombinedCreditsResponse {

FILE: server/interfaces/api/plexInterfaces.ts
  type PlexStatus (line 3) | interface PlexStatus {
  type PlexConnection (line 9) | interface PlexConnection {
  type PlexDevice (line 19) | interface PlexDevice {

FILE: server/interfaces/api/requestInterfaces.ts
  type RequestResultsResponse (line 5) | interface RequestResultsResponse extends PaginatedResponse {
  type MediaRequestBody (line 16) | type MediaRequestBody = {

FILE: server/interfaces/api/serviceInterfaces.ts
  type ServiceCommonServer (line 4) | interface ServiceCommonServer {
  type ServiceCommonServerWithDetails (line 19) | interface ServiceCommonServerWithDetails {

FILE: server/interfaces/api/settingsInterfaces.ts
  type LogMessage (line 4) | type LogMessage = {
  type LogsResultsResponse (line 12) | interface LogsResultsResponse extends PaginatedResponse {
  type SettingsAboutResponse (line 16) | interface SettingsAboutResponse {
  type PublicSettingsResponse (line 24) | interface PublicSettingsResponse {
  type CacheItem (line 53) | interface CacheItem {
  type CacheResponse (line 65) | interface CacheResponse {
  type StatusResponse (line 74) | interface StatusResponse {

FILE: server/interfaces/api/userInterfaces.ts
  type UserResultsResponse (line 6) | interface UserResultsResponse extends PaginatedResponse {
  type UserRequestsResponse (line 10) | interface UserRequestsResponse extends PaginatedResponse {
  type QuotaStatus (line 14) | interface QuotaStatus {
  type QuotaResponse (line 22) | interface QuotaResponse {
  type UserWatchDataResponse (line 27) | interface UserWatchDataResponse {

FILE: server/interfaces/api/userSettingsInterfaces.ts
  type UserSettingsGeneralResponse (line 3) | interface UserSettingsGeneralResponse {
  type NotificationAgentTypes (line 23) | type NotificationAgentTypes = Record<NotificationAgentKey, number>;
  type UserSettingsNotificationsResponse (line 24) | interface UserSettingsNotificationsResponse {

FILE: server/job/blocklistedTagsProcessor.ts
  constant TMDB_API_DELAY_MS (line 20) | const TMDB_API_DELAY_MS = 250;
  class AbortTransaction (line 21) | class AbortTransaction extends Error {}
  class BlocklistedTagProcessor (line 23) | class BlocklistedTagProcessor implements RunnableScanner<StatusBase> {
    method run (line 28) | public async run() {
    method status (line 49) | public status(): StatusBase {
    method cancel (line 57) | public cancel() {
    method reset (line 63) | private reset() {
    method createBlocklistEntries (line 67) | private async createBlocklistEntries(em: EntityManager) {
    method processResults (line 166) | private async processResults(
    method cleanBlocklist (line 207) | private async cleanBlocklist(em: EntityManager) {

FILE: server/job/schedule.ts
  type ScheduledJob (line 20) | interface ScheduledJob {

FILE: server/lib/availabilitySync.ts
  class AvailabilitySync (line 20) | class AvailabilitySync {
    method run (line 32) | async run() {
    method cancel (line 414) | public cancel() {
    method loadAvailableMediaPaginated (line 418) | private async *loadAvailableMediaPaginated(pageSize: number) {
    method mediaUpdater (line 444) | private async mediaUpdater(
    method seasonUpdater (line 536) | private async seasonUpdater(
    method mediaExistsInRadarr (line 609) | private async mediaExistsInRadarr(
    method mediaExistsInSonarr (line 682) | private async mediaExistsInSonarr(
    method seasonExistsInSonarr (line 763) | private async seasonExistsInSonarr(
    method mediaExistsInPlex (line 804) | private async mediaExistsInPlex(
    method seasonExistsInPlex (line 933) | private async seasonExistsInPlex(
    method mediaExistsInJellyfin (line 965) | private async mediaExistsInJellyfin(
    method seasonExistsInJellyfin (line 1050) | private async seasonExistsInJellyfin(

FILE: server/lib/cache.ts
  type AvailableCacheIds (line 3) | type AvailableCacheIds =
  constant DEFAULT_TTL (line 15) | const DEFAULT_TTL = 300;
  constant DEFAULT_CHECK_PERIOD (line 16) | const DEFAULT_CHECK_PERIOD = 120;
  class Cache (line 18) | class Cache {
    method constructor (line 23) | constructor(
    method getStats (line 36) | public getStats() {
    method flush (line 40) | public flush(): void {
  class CacheManager (line 45) | class CacheManager {
    method getCache (line 80) | public getCache(id: AvailableCacheIds): Cache {
    method getAllCaches (line 84) | public getAllCaches(): Record<string, Cache> {

FILE: server/lib/downloadtracker.ts
  type EpisodeNumberResult (line 8) | interface EpisodeNumberResult {
  type DownloadingItem (line 14) | interface DownloadingItem {
  class DownloadTracker (line 27) | class DownloadTracker {
    method getMovieProgress (line 31) | public getMovieProgress(
    method getSeriesProgress (line 44) | public getSeriesProgress(
    method resetDownloadTracker (line 57) | public async resetDownloadTracker() {
    method updateDownloads (line 62) | public updateDownloads() {
    method updateRadarrDownloads (line 67) | private async updateRadarrDownloads() {
    method updateSonarrDownloads (line 145) | private async updateSonarrDownloads() {

FILE: server/lib/email/index.ts
  class PreparedEmail (line 8) | class PreparedEmail extends Email {
    method constructor (line 9) | public constructor(settings: NotificationAgentEmail, pgpKey?: string) {

FILE: server/lib/email/openpgpEncrypt.ts
  type EncryptorOptions (line 7) | interface EncryptorOptions {
  class PGPEncryptor (line 13) | class PGPEncryptor extends Transform {
    method constructor (line 21) | constructor(options: EncryptorOptions) {

FILE: server/lib/imageproxy.ts
  type ImageResponse (line 10) | type ImageResponse = {
  class ImageProxy (line 27) | class ImageProxy {
    method clearCache (line 28) | public static async clearCache(key: string) {
    method getImageStats (line 75) | public static async getImageStats(
    method getDirectorySize (line 89) | private static async getDirectorySize(dir: string): Promise<number> {
    method getImageCount (line 121) | private static async getImageCount(dir: string) {
    method constructor (line 139) | constructor(
    method getImage (line 161) | public async getImage(
    method clearCachedImage (line 191) | public async clearCachedImage(path: string) {
    method get (line 232) | private async get(cacheKey: string): Promise<ImageResponse | null> {
    method set (line 264) | private async set(
    method writeToCacheDir (line 317) | private async writeToCacheDir(
    method getCacheKey (line 335) | private getCacheKey(path: string) {
    method getHash (line 339) | private getHash(items: (string | number | Buffer)[]) {
    method getCacheDirectory (line 351) | private getCacheDirectory() {

FILE: server/lib/notifications/agents/agent.ts
  type NotificationPayload (line 9) | interface NotificationPayload {
  method constructor (line 28) | public constructor(settings?: T) {
  type NotificationAgent (line 35) | interface NotificationAgent {

FILE: server/lib/notifications/agents/discord.ts
  type EmbedColors (line 16) | enum EmbedColors {
  type DiscordImageEmbed (line 42) | interface DiscordImageEmbed {
  type Field (line 49) | interface Field {
  type DiscordRichEmbed (line 54) | interface DiscordRichEmbed {
  type DiscordWebhookPayload (line 81) | interface DiscordWebhookPayload {
  class DiscordAgent (line 94) | class DiscordAgent
    method getSettings (line 98) | protected getSettings(): NotificationAgentDiscord {
    method buildEmbed (line 108) | public buildEmbed(
    method shouldSend (line 233) | public shouldSend(): boolean {
    method send (line 243) | public async send(

FILE: server/lib/notifications/agents/email.ts
  class EmailAgent (line 16) | class EmailAgent
    method getSettings (line 20) | protected getSettings(): NotificationAgentEmail {
    method shouldSend (line 30) | public shouldSend(): boolean {
    method buildMessage (line 45) | private buildMessage(
    method send (line 197) | public async send(

FILE: server/lib/notifications/agents/gotify.ts
  type GotifyPayload (line 10) | interface GotifyPayload {
  class GotifyAgent (line 17) | class GotifyAgent
    method getSettings (line 21) | protected getSettings(): NotificationAgentGotify {
    method shouldSend (line 31) | public shouldSend(): boolean {
    method getNotificationPayload (line 46) | private getNotificationPayload(
    method send (line 121) | public async send(

FILE: server/lib/notifications/agents/ntfy.ts
  class NtfyAgent (line 10) | class NtfyAgent
    method getSettings (line 14) | protected getSettings(): NotificationAgentNtfy {
    method buildPayload (line 24) | private buildPayload(type: Notification, payload: NotificationPayload) {
    method shouldSend (line 94) | public shouldSend(): boolean {
    method send (line 104) | public async send(

FILE: server/lib/notifications/agents/pushbullet.ts
  type PushbulletPayload (line 17) | interface PushbulletPayload {
  class PushbulletAgent (line 24) | class PushbulletAgent
    method getSettings (line 28) | protected getSettings(): NotificationAgentPushbullet {
    method shouldSend (line 38) | public shouldSend(): boolean {
    method getNotificationPayload (line 42) | private getNotificationPayload(
    method send (line 104) | public async send(

FILE: server/lib/notifications/agents/pushover.ts
  type PushoverImagePayload (line 17) | interface PushoverImagePayload {
  type PushoverPayload (line 22) | interface PushoverPayload extends PushoverImagePayload {
  class PushoverAgent (line 33) | class PushoverAgent
    method getSettings (line 37) | protected getSettings(): NotificationAgentPushover {
    method shouldSend (line 47) | public shouldSend(): boolean {
    method getImagePayload (line 61) | private async getImagePayload(
    method getNotificationPayload (line 87) | private async getNotificationPayload(
    method send (line 190) | public async send(

FILE: server/lib/notifications/agents/slack.ts
  type EmbedField (line 10) | interface EmbedField {
  type TextItem (line 15) | interface TextItem {
  type Element (line 21) | interface Element {
  type EmbedBlock (line 30) | interface EmbedBlock {
  type SlackBlockEmbed (line 43) | interface SlackBlockEmbed {
  class SlackAgent (line 48) | class SlackAgent
    method getSettings (line 52) | protected getSettings(): NotificationAgentSlack {
    method buildEmbed (line 62) | public buildEmbed(
    method shouldSend (line 215) | public shouldSend(): boolean {
    method send (line 225) | public async send(

FILE: server/lib/notifications/agents/telegram.ts
  type TelegramMessagePayload (line 17) | interface TelegramMessagePayload {
  type TelegramPhotoPayload (line 25) | interface TelegramPhotoPayload {
  class TelegramAgent (line 34) | class TelegramAgent
    method getSettings (line 40) | protected getSettings(): NotificationAgentTelegram {
    method shouldSend (line 50) | public shouldSend(): boolean {
    method escapeText (line 60) | private escapeText(text: string | undefined): string {
    method getNotificationPayload (line 64) | private getNotificationPayload(
    method send (line 159) | public async send(

FILE: server/lib/notifications/agents/webhook.ts
  type KeyMapFunction (line 12) | type KeyMapFunction = (
  class WebhookAgent (line 63) | class WebhookAgent
    method getSettings (line 67) | protected getSettings(): NotificationAgentWebhook {
    method parseKeys (line 77) | private parseKeys(
    method buildPayload (line 143) | private buildPayload(type: Notification, payload: NotificationPayload) {
    method shouldSend (line 154) | public shouldSend(): boolean {
    method send (line 164) | public async send(

FILE: server/lib/notifications/agents/webpush.ts
  type PushNotificationPayload (line 15) | interface PushNotificationPayload {
  type WebPushError (line 27) | interface WebPushError extends Error {
  class WebPushAgent (line 36) | class WebPushAgent
    method getSettings (line 40) | protected getSettings(): NotificationAgentConfig {
    method getNotificationPayload (line 50) | private getNotificationPayload(
    method shouldSend (line 151) | public shouldSend(): boolean {
    method send (line 159) | public async send(

FILE: server/lib/notifications/index.ts
  type Notification (line 6) | enum Notification {
  class NotificationManager (line 92) | class NotificationManager {
    method sendNotification (line 100) | public sendNotification(

FILE: server/lib/overseerrMerge.ts
  class SeerrMigration1759769291608 (line 148) | class SeerrMigration1759769291608 implements MigrationInterface {
    method up (line 151) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 351) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/lib/permissions.ts
  type Permission (line 1) | enum Permission {
  type PermissionCheckOptions (line 34) | interface PermissionCheckOptions {

FILE: server/lib/refreshToken.ts
  class RefreshToken (line 6) | class RefreshToken {
    method run (line 7) | public async run() {
    method refreshUserToken (line 21) | private async refreshUserToken(user: User) {

FILE: server/lib/scanners/baseScanner.ts
  constant BUNDLE_SIZE (line 12) | const BUNDLE_SIZE = 20;
  constant UPDATE_RATE (line 13) | const UPDATE_RATE = 4 * 1000;
  type StatusBase (line 15) | type StatusBase = {
  type RunnableScanner (line 21) | interface RunnableScanner<T> {
  type MediaIds (line 26) | interface MediaIds {
  type ProcessOptions (line 33) | interface ProcessOptions {
  type ProcessableSeason (line 46) | interface ProcessableSeason {
  class BaseScanner (line 55) | class BaseScanner<T> {
    method constructor (line 69) | protected constructor(
    method getExisting (line 84) | private async getExisting(tmdbId: number, mediaType: MediaType) {
    method processMovie (line 94) | protected async processMovie(
    method processShow (line 251) | protected async processShow(
    method startRun (line 599) | protected startRun(): string {
    method endRun (line 630) | protected endRun(sessionId: string): void {
    method cancel (line 636) | public cancel(): void {
    method loop (line 640) | protected async loop(
    method processItems (line 680) | private async processItems(
    method log (line 691) | protected log(
    method protectedUpdateRate (line 699) | get protectedUpdateRate(): number {
    method protectedBundleSize (line 703) | get protectedBundleSize(): number {

FILE: server/lib/scanners/jellyfin/index.ts
  type JellyfinSyncStatus (line 28) | interface JellyfinSyncStatus extends StatusBase {
  class JellyfinScanner (line 33) | class JellyfinScanner
    method constructor (line 43) | constructor({ isRecentOnly }: { isRecentOnly?: boolean } = {}) {
    method extractMovieIds (line 48) | private async extractMovieIds(jellyfinitem: JellyfinLibraryItem): Prom...
    method processJellyfinMovie (line 123) | private async processJellyfinMovie(jellyfinitem: JellyfinLibraryItem) {
    method getTvShow (line 181) | private async getTvShow({
    method processJellyfinShow (line 217) | private async processJellyfinShow(jellyfinitem: JellyfinLibraryItem) {
    method processItem (line 446) | private async processItem(item: JellyfinLibraryItem): Promise<void> {
    method run (line 454) | public async run(): Promise<void> {
    method status (line 542) | public status(): JellyfinSyncStatus {

FILE: server/lib/scanners/plex/index.ts
  constant HAMA_AGENT (line 34) | const HAMA_AGENT = 'com.plexapp.agents.hama';
  type SyncStatus (line 36) | type SyncStatus = StatusBase & {
  class PlexScanner (line 41) | class PlexScanner
    method constructor (line 50) | public constructor(isRecentOnly = false) {
    method status (line 55) | public status(): SyncStatus {
    method run (line 65) | public async run(): Promise<void> {
    method paginateLibrary (line 162) | private async paginateLibrary(
    method processItem (line 208) | private async processItem(plexitem: PlexLibraryItem) {
    method processPlexMovie (line 227) | private async processPlexMovie(plexitem: PlexLibraryItem) {
    method processPlexMovieByTmdbId (line 242) | private async processPlexMovieByTmdbId(
    method getTvShow (line 258) | private async getTvShow({
    method processPlexShow (line 294) | private async processPlexShow(plexitem: PlexLibraryItem) {
    method getMediaIds (line 382) | private async getMediaIds(plexitem: PlexLibraryItem): Promise<MediaIds> {
    method processHamaMovie (line 555) | private async processHamaMovie(metadata: PlexMetadata, tmdbId: number) {
    method processHamaSpecials (line 568) | private async processHamaSpecials(metadata: PlexMetadata, tvdbId: numb...
    method hasHamaAgent (line 595) | private async hasHamaAgent() {

FILE: server/lib/scanners/radarr/index.ts
  type SyncStatus (line 12) | type SyncStatus = StatusBase & {
  class RadarrScanner (line 17) | class RadarrScanner
    method constructor (line 25) | constructor() {
    method status (line 29) | public status(): SyncStatus {
    method run (line 39) | public async run(): Promise<void> {
    method processRadarrMovie (line 81) | private async processRadarrMovie(radarrMovie: RadarrMovie): Promise<vo...

FILE: server/lib/scanners/sonarr/index.ts
  type SyncStatus (line 22) | type SyncStatus = StatusBase & {
  class SonarrScanner (line 27) | class SonarrScanner
    method constructor (line 35) | constructor() {
    method status (line 39) | public status(): SyncStatus {
    method run (line 49) | public async run(): Promise<void> {
    method processSonarrSeries (line 91) | private async processSonarrSeries(sonarrSeries: SonarrSeries) {

FILE: server/lib/search.ts
  type SearchProvider (line 24) | interface SearchProvider {

FILE: server/lib/settings/index.ts
  type Library (line 16) | interface Library {
  type Region (line 24) | interface Region {
  type Language (line 30) | interface Language {
  type PlexSettings (line 36) | interface PlexSettings {
  type JellyfinSettings (line 46) | interface JellyfinSettings {
  type TautulliSettings (line 58) | interface TautulliSettings {
  type DVRSettings (line 67) | interface DVRSettings {
  type RadarrSettings (line 88) | interface RadarrSettings extends DVRSettings {
  type SonarrSettings (line 92) | interface SonarrSettings extends DVRSettings {
  type Quota (line 105) | interface Quota {
  type MetadataProviderType (line 110) | enum MetadataProviderType {
  type MetadataSettings (line 115) | interface MetadataSettings {
  type ProxySettings (line 120) | interface ProxySettings {
  type MainSettings (line 131) | interface MainSettings {
  type ProxySettings (line 158) | interface ProxySettings {
  type DnsCacheSettings (line 169) | interface DnsCacheSettings {
  type NetworkSettings (line 175) | interface NetworkSettings {
  type PublicSettings (line 184) | interface PublicSettings {
  type FullPublicSettings (line 188) | interface FullPublicSettings extends PublicSettings {
  type NotificationAgentConfig (line 216) | interface NotificationAgentConfig {
  type NotificationAgentDiscord (line 222) | interface NotificationAgentDiscord extends NotificationAgentConfig {
  type NotificationAgentSlack (line 232) | interface NotificationAgentSlack extends NotificationAgentConfig {
  type NotificationAgentEmail (line 238) | interface NotificationAgentEmail extends NotificationAgentConfig {
  type NotificationAgentTelegram (line 256) | interface NotificationAgentTelegram extends NotificationAgentConfig {
  type NotificationAgentPushbullet (line 266) | interface NotificationAgentPushbullet extends NotificationAgentConfig {
  type NotificationAgentPushover (line 273) | interface NotificationAgentPushover extends NotificationAgentConfig {
  type NotificationAgentWebhook (line 281) | interface NotificationAgentWebhook extends NotificationAgentConfig {
  type NotificationAgentGotify (line 291) | interface NotificationAgentGotify extends NotificationAgentConfig {
  type NotificationAgentNtfy (line 299) | interface NotificationAgentNtfy extends NotificationAgentConfig {
  type NotificationAgentKey (line 312) | enum NotificationAgentKey {
  type NotificationAgents (line 325) | interface NotificationAgents {
  type NotificationSettings (line 338) | interface NotificationSettings {
  type JobSettings (line 342) | interface JobSettings {
  type JobId (line 346) | type JobId =
  type AllSettings (line 361) | interface AllSettings {
  constant SETTINGS_PATH (line 379) | const SETTINGS_PATH = process.env.CONFIG_DIRECTORY
  class Settings (line 383) | class Settings {
    method constructor (line 387) | constructor(initialSettings?: AllSettings) {
    method main (line 617) | get main(): MainSettings {
    method main (line 621) | set main(data: MainSettings) {
    method plex (line 625) | get plex(): PlexSettings {
    method plex (line 629) | set plex(data: PlexSettings) {
    method jellyfin (line 633) | get jellyfin(): JellyfinSettings {
    method jellyfin (line 637) | set jellyfin(data: JellyfinSettings) {
    method tautulli (line 641) | get tautulli(): TautulliSettings {
    method tautulli (line 645) | set tautulli(data: TautulliSettings) {
    method metadataSettings (line 649) | get metadataSettings(): MetadataSettings {
    method metadataSettings (line 653) | set metadataSettings(data: MetadataSettings) {
    method radarr (line 660) | get radarr(): RadarrSettings[] {
    method radarr (line 664) | set radarr(data: RadarrSettings[]) {
    method sonarr (line 668) | get sonarr(): SonarrSettings[] {
    method sonarr (line 672) | set sonarr(data: SonarrSettings[]) {
    method public (line 676) | get public(): PublicSettings {
    method public (line 680) | set public(data: PublicSettings) {
    method fullPublicSettings (line 684) | get fullPublicSettings(): FullPublicSettings {
    method notifications (line 719) | get notifications(): NotificationSettings {
    method notifications (line 723) | set notifications(data: NotificationSettings) {
    method jobs (line 727) | get jobs(): Record<JobId, JobSettings> {
    method jobs (line 731) | set jobs(data: Record<JobId, JobSettings>) {
    method network (line 735) | get network(): NetworkSettings {
    method network (line 739) | set network(data: NetworkSettings) {
    method migrations (line 743) | get migrations(): string[] {
    method migrations (line 747) | set migrations(data: string[]) {
    method clientId (line 751) | get clientId(): string {
    method vapidPublic (line 755) | get vapidPublic(): string {
    method vapidPrivate (line 759) | get vapidPrivate(): string {
    method regenerateApiKey (line 763) | public async regenerateApiKey(): Promise<MainSettings> {
    method generateApiKey (line 769) | private generateApiKey(): string {
    method load (line 786) | public async load(
    method save (line 837) | public async save(): Promise<void> {

FILE: server/lib/watchlistsync.ts
  class WatchlistSync (line 17) | class WatchlistSync {
    method syncWatchlist (line 18) | public async syncWatchlist() {
    method syncUserWatchlist (line 34) | private async syncUserWatchlist(user: User) {

FILE: server/middleware/deprecation.ts
  type DeprecationOptions (line 4) | interface DeprecationOptions {

FILE: server/migration/postgres/1734786061496-InitialMigration.ts
  class InitialMigration1734786061496 (line 3) | class InitialMigration1734786061496 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 114) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/postgres/1734786596045-AddTelegramMessageThreadId.ts
  class AddTelegramMessageThreadId1734786596045 (line 3) | class AddTelegramMessageThreadId1734786596045 implements MigrationInterf...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 12) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/postgres/1734805738349-AddOverrideRules.ts
  class AddOverrideRules1734805738349 (line 3) | class AddOverrideRules1734805738349 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 12) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/postgres/1734809898562-FixNullFields.ts
  class FixNullFields1734809898562 (line 3) | class FixNullFields1734809898562 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 36) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/postgres/1737320080282-AddBlacklistTagsColumn.ts
  class AddBlacklistTagsColumn1737320080282 (line 3) | class AddBlacklistTagsColumn1737320080282 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 12) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/postgres/1743023615532-UpdateWebPush.ts
  class UpdateWebPush1743023615532 (line 3) | class UpdateWebPush1743023615532 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 18) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/postgres/1743107707465-AddUserAvatarCacheFields.ts
  class AddUserAvatarCacheFields1743107707465 (line 3) | class AddUserAvatarCacheFields1743107707465 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 15) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/postgres/1745492376568-UpdateWebPush.ts
  class UpdateWebPush1745492376568 (line 3) | class UpdateWebPush1745492376568 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 12) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/postgres/1746811308203-FixIssueTimestamps.ts
  class FixIssueTimestamps1746811308203 (line 3) | class FixIssueTimestamps1746811308203 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 119) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/postgres/1765233385034-AddUniqueConstraintToPushSubscription.ts
  class AddUniqueConstraintToPushSubscription1765233385034 (line 3) | class AddUniqueConstraintToPushSubscription1765233385034 implements Migr...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 21) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/postgres/1770627987304-AddPerformanceIndexes.ts
  class AddPerformanceIndexes1770627987304 (line 3) | class AddPerformanceIndexes1770627987304 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 21) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/postgres/1771080196816-RenameBlacklistToBlocklist.ts
  class RenameBlacklistToBlocklist1771080196816 (line 3) | class RenameBlacklistToBlocklist1771080196816 implements MigrationInterf...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 13) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/postgres/1771259406751-AddForeignKeyIndexes.ts
  class AddForeignKeyIndexes1771259406751 (line 3) | class AddForeignKeyIndexes1771259406751 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 81) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/postgres/1771337333450-RecoveryLinkExpirationDateTime.ts
  class RecoveryLinkExpirationDateTime1771337333450 (line 3) | class RecoveryLinkExpirationDateTime1771337333450 implements MigrationIn...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 12) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/postgres/1772000000000-FixBlocklistIdDefault.ts
  class FixBlocklistIdDefault1772000000000 (line 3) | class FixBlocklistIdDefault1772000000000 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 16) | public async down(): Promise<void> {

FILE: server/migration/postgres/1772048000333-AddMediaTypeToUniqueConstraints.ts
  class AddMediaTypeToUniqueConstraints1772048000333 (line 3) | class AddMediaTypeToUniqueConstraints1772048000333 implements MigrationI...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 30) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1603944374840-InitialMigration.ts
  class InitialMigration1603944374840 (line 3) | class InitialMigration1603944374840 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 56) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1605085519544-SeasonStatus.ts
  class SeasonStatus1605085519544 (line 3) | class SeasonStatus1605085519544 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 12) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1606730060700-CascadeMigration.ts
  class CascadeMigration1606730060700 (line 3) | class CascadeMigration1606730060700 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 59) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1607928251245-DropImdbIdConstraint.ts
  class DropImdbIdConstraint1607928251245 (line 4) | class DropImdbIdConstraint1607928251245 implements MigrationInterface {
    method up (line 5) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 12) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1608217312474-AddUserRequestDeleteCascades.ts
  class AddUserRequestDeleteCascades1608219049304 (line 3) | class AddUserRequestDeleteCascades1608219049304 implements MigrationInte...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 19) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1608477467935-AddLastSeasonChangeMedia.ts
  class AddLastSeasonChangeMedia1608477467935 (line 3) | class AddLastSeasonChangeMedia1608477467935 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 29) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1608477467936-ForceDropImdbUniqueConstraint.ts
  class ForceDropImdbUniqueConstraint1608477467935 (line 3) | class ForceDropImdbUniqueConstraint1608477467935 implements MigrationInt...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 29) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1609236552057-RemoveTmdbIdUniqueConstraint.ts
  class RemoveTmdbIdUniqueConstraint1609236552057 (line 3) | class RemoveTmdbIdUniqueConstraint1609236552057 implements MigrationInte...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 29) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1610070934506-LocalUsers.ts
  class LocalUsers1610070934506 (line 3) | class LocalUsers1610070934506 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 25) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1610370640747-Add4kStatusFields.ts
  class Add4kStatusFields1610370640747 (line 3) | class Add4kStatusFields1610370640747 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 49) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1610522845513-AddMediaAddedFieldToMedia.ts
  class AddMediaAddedFieldToMedia1610522845513 (line 3) | class AddMediaAddedFieldToMedia1610522845513 implements MigrationInterfa...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 29) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1611508672722-AddDisplayNameToUser.ts
  class AddDisplayNameToUser1611508672722 (line 3) | class AddDisplayNameToUser1611508672722 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 25) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1611757511674-SonarrRadarrSyncServiceFields.ts
  class SonarrRadarrSyncServiceFields1611757511674 (line 3) | class SonarrRadarrSyncServiceFields1611757511674 implements MigrationInt...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 29) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1611801511397-AddRatingKeysToMedia.ts
  class AddRatingKeysToMedia1611801511397 (line 3) | class AddRatingKeysToMedia1611801511397 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 29) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1612482778137-AddResetPasswordGuidAndExpiryDate.ts
  class AddResetPasswordGuidAndExpiryDate1612482778137 (line 3) | class AddResetPasswordGuidAndExpiryDate1612482778137 implements Migratio...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 17) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1612571545781-AddLanguageProfileId.ts
  class AddLanguageProfileId1612571545781 (line 3) | class AddLanguageProfileId1612571545781 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 19) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1613379909641-AddJellyfinUserParams.ts
  class AddJellyfinUserParams1613379909641 (line 3) | class AddJellyfinUserParams1613379909641 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 37) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1613412948344-ServerTypeEnum.ts
  class ServerTypeEnum1613412948344 (line 3) | class ServerTypeEnum1613412948344 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 49) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1613615266968-CreateUserSettings.ts
  class CreateUserSettings1613615266968 (line 3) | class CreateUserSettings1613615266968 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 22) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1613670041760-AddJellyfinDeviceId.ts
  class AddJellyfinDeviceId1613670041760 (line 3) | class AddJellyfinDeviceId1613670041760 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 65) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1613955393450-UpdateUserSettingsRegions.ts
  class UpdateUserSettingsRegions1613955393450 (line 3) | class UpdateUserSettingsRegions1613955393450 implements MigrationInterfa...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 19) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1614334195680-AddTelegramSettingsToUserSettings.ts
  class AddTelegramSettingsToUserSettings1614334195680 (line 3) | class AddTelegramSettingsToUserSettings1614334195680 implements Migratio...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 19) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1615333940450-AddPGPToUserSettings.ts
  class AddPGPToUserSettings1615333940450 (line 3) | class AddPGPToUserSettings1615333940450 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 19) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1616576677254-AddUserQuotaFields.ts
  class AddUserQuotaFields1616576677254 (line 3) | class AddUserQuotaFields1616576677254 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 17) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1617624225464-CreateTagsFieldonMediaRequest.ts
  class CreateTagsFieldonMediaRequest1617624225464 (line 3) | class CreateTagsFieldonMediaRequest1617624225464 implements MigrationInt...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 19) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1617730837489-AddUserSettingsNotificationAgentsField.ts
  class AddUserSettingsNotificationAgentsField1617730837489 (line 3) | class AddUserSettingsNotificationAgentsField1617730837489 implements Mig...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 29) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1618912653565-CreateUserPushSubscriptions.ts
  class CreateUserPushSubscriptions1618912653565 (line 3) | class CreateUserPushSubscriptions1618912653565 implements MigrationInter...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 22) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1619239659754-AddUserSettingsLocale.ts
  class AddUserSettingsLocale1619239659754 (line 3) | class AddUserSettingsLocale1619239659754 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 19) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1619339817343-AddUserSettingsNotificationTypes.ts
  class AddUserSettingsNotificationTypes1619339817343 (line 3) | class AddUserSettingsNotificationTypes1619339817343 implements Migration...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 29) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1634904083966-AddIssues.ts
  class AddIssues1634904083966 (line 3) | class AddIssues1634904083966 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 33) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1635079863457-AddPushbulletPushoverUserSettings.ts
  class AddPushbulletPushoverUserSettings1635079863457 (line 3) | class AddPushbulletPushoverUserSettings1635079863457 implements Migratio...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 19) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1660632269368-AddWatchlistSyncUserSetting.ts
  class AddWatchlistSyncUserSetting1660632269368 (line 3) | class AddWatchlistSyncUserSetting1660632269368 implements MigrationInter...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 19) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1660714479373-AddMediaRequestIsAutoRequestedField.ts
  class AddMediaRequestIsAutoRequestedField1660714479373 (line 3) | class AddMediaRequestIsAutoRequestedField1660714479373 implements Migrat...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 19) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1672041273674-AddDiscoverSlider.ts
  class AddDiscoverSlider1672041273674 (line 3) | class AddDiscoverSlider1672041273674 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 12) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1682608634546-AddWatchlists.ts
  class AddWatchlists1682608634546 (line 3) | class AddWatchlists1682608634546 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 15) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1697393491630-AddUserPushoverSound.ts
  class AddUserPushoverSound1697393491630 (line 3) | class AddUserPushoverSound1697393491630 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 19) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1699901142442-AddBlacklist.ts
  class AddBlacklist1699901142442 (line 3) | class AddBlacklist1699901142442 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 16) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1727907530757-AddUserSettingsStreamingRegion.ts
  class AddUserSettingsStreamingRegion1727907530757 (line 3) | class AddUserSettingsStreamingRegion1727907530757 implements MigrationIn...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 29) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1734287582736-AddTelegramMessageThreadId.ts
  class AddTelegramMessageThreadId1734287582736 (line 3) | class AddTelegramMessageThreadId1734287582736 implements MigrationInterf...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 19) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1734805733535-AddOverrideRules.ts
  class AddOverrideRules1734805733535 (line 3) | class AddOverrideRules1734805733535 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 12) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1737320080282-AddBlacklistTagsColumn.ts
  class AddBlacklistTagsColumn1737320080282 (line 3) | class AddBlacklistTagsColumn1737320080282 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 22) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1743023610704-UpdateWebPush.ts
  class UpdateWebPush1743023610704 (line 3) | class UpdateWebPush1743023610704 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 105) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1743107645301-AddUserAvatarCacheFields.ts
  class AddUserAvatarCacheFields1743107645301 (line 3) | class AddUserAvatarCacheFields1743107645301 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 37) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1745492372230-UpdateWebPush.ts
  class UpdateWebPush1745492372230 (line 3) | class UpdateWebPush1745492372230 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 43) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1765233385034-AddUniqueConstraintToPushSubscription.ts
  class AddUniqueConstraintToPushSubscription1765233385034 (line 3) | class AddUniqueConstraintToPushSubscription1765233385034 implements Migr...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 21) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1770627968781-AddPerformanceIndexes.ts
  class AddPerformanceIndexes1770627968781 (line 3) | class AddPerformanceIndexes1770627968781 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 188) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1771080196816-RenameBlacklistToBlocklist.ts
  class RenameBlacklistToBlocklist1771080196816 (line 3) | class RenameBlacklistToBlocklist1771080196816 implements MigrationInterf...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 36) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1771259394105-AddForeignKeyIndexes.ts
  class AddForeignKeyIndexes1771259394105 (line 3) | class AddForeignKeyIndexes1771259394105 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 120) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1771337037917-RecoveryLinkExpirationDateTime.ts
  class RecoveryLinkExpirationDateTime1771337037917 (line 3) | class RecoveryLinkExpirationDateTime1771337037917 implements MigrationIn...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 17) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/migration/sqlite/1772047972752-AddMediaTypeToUniqueConstraints.ts
  class AddMediaTypeToUniqueConstraints1772047972752 (line 3) | class AddMediaTypeToUniqueConstraints1772047972752 implements MigrationI...
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 116) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: server/models/Collection.ts
  type Collection (line 8) | interface Collection {

FILE: server/models/Movie.ts
  type Video (line 24) | interface Video {
  type MovieDetails (line 40) | interface MovieDetails {

FILE: server/models/Person.ts
  type PersonDetails (line 8) | interface PersonDetails {
  type PersonCredit (line 25) | interface PersonCredit {
  type PersonCreditCast (line 50) | interface PersonCreditCast extends PersonCredit {
  type PersonCreditCrew (line 54) | interface PersonCreditCrew extends PersonCredit {
  type CombinedCredit (line 59) | interface CombinedCredit {

FILE: server/models/Search.ts
  type MediaType (line 13) | type MediaType = 'tv' | 'movie' | 'person' | 'collection';
  type SearchResult (line 15) | interface SearchResult {
  type MovieResult (line 29) | interface MovieResult extends SearchResult {
  type TvResult (line 39) | interface TvResult extends SearchResult {
  type CollectionResult (line 47) | interface CollectionResult {
  type PersonResult (line 59) | interface PersonResult {
  type Results (line 69) | type Results = MovieResult | TvResult | PersonResult | CollectionResult;

FILE: server/models/Tv.ts
  type Episode (line 29) | interface Episode {
  type Season (line 43) | interface Season {
  type SeasonWithEpisodes (line 53) | interface SeasonWithEpisodes extends Omit<Season, 'episodeCount'> {
  type SpokenLanguage (line 58) | interface SpokenLanguage {
  type TvDetails (line 64) | interface TvDetails {

FILE: server/models/common.ts
  type ProductionCompany (line 13) | interface ProductionCompany {
  type TvNetwork (line 23) | interface TvNetwork {
  type Keyword (line 32) | interface Keyword {
  type Genre (line 37) | interface Genre {
  type Cast (line 42) | interface Cast {
  type Crew (line 53) | interface Crew {
  type ExternalIds (line 63) | interface ExternalIds {
  type WatchProviders (line 74) | interface WatchProviders {
  type WatchProviderDetails (line 81) | interface WatchProviderDetails {

FILE: server/routes/auth.test.ts
  function createApp (line 22) | function createApp() {
  function authenticatedAgent (line 59) | async function authenticatedAgent(email: string, password: string) {
  function getResetGuid (line 290) | async function getResetGuid(email: string): Promise<string> {

FILE: server/routes/auth.ts
  function getUserAvatarUrl (line 222) | function getUserAvatarUrl(user: User): string {

FILE: server/routes/avatarproxy.ts
  function initAvatarImageProxy (line 18) | async function initAvatarImageProxy() {
  function getJellyfinAvatarUrl (line 37) | function getJellyfinAvatarUrl(userId: string) {
  function computeImageHash (line 44) | function computeImageHash(buffer: Buffer): string {
  function checkAvatarChanged (line 48) | async function checkAvatarChanged(

FILE: server/routes/discover.ts
  type FilterOptions (line 83) | type FilterOptions = z.infer<typeof QueryFilterOptions>;

FILE: server/routes/imageproxy.ts
  function initTmdbImageProxy (line 9) | function initTmdbImageProxy() {
  function initTvdbImageProxy (line 21) | function initTvdbImageProxy() {

FILE: server/routes/settings/metadata.ts
  function getTestResultString (line 11) | function getTestResultString(testValue: number): string {

FILE: server/subscriber/IssueCommentSubscriber.ts
  class IssueCommentSubscriber (line 16) | class IssueCommentSubscriber implements EntitySubscriberInterface<IssueC...
    method listenTo (line 17) | public listenTo(): typeof IssueComment {
    method sendIssueCommentNotification (line 21) | private async sendIssueCommentNotification(entity: IssueComment) {
    method afterInsert (line 95) | public afterInsert(event: InsertEvent<IssueComment>): void {

FILE: server/subscriber/IssueSubscriber.ts
  class IssueSubscriber (line 17) | class IssueSubscriber implements EntitySubscriberInterface<Issue> {
    method listenTo (line 18) | public listenTo(): typeof Issue {
    method sendIssueNotification (line 22) | private async sendIssueNotification(entity: Issue, type: Notification) {
    method afterInsert (line 105) | public afterInsert(event: InsertEvent<Issue>): void {
    method beforeUpdate (line 113) | public beforeUpdate(event: UpdateEvent<Issue>): void {

FILE: server/subscriber/MediaRequestSubscriber.ts
  class MediaRequestSubscriber (line 44) | class MediaRequestSubscriber implements EntitySubscriberInterface<MediaR...
    method notifyAvailableMovie (line 45) | private async notifyAvailableMovie(
    method notifyAvailableSeries (line 104) | private async notifyAvailableSeries(
    method sendToRadarr (line 183) | public async sendToRadarr(entity: MediaRequest): Promise<void> {
    method sendToSonarr (line 477) | public async sendToSonarr(entity: MediaRequest): Promise<void> {
    method updateParentStatus (line 820) | public async updateParentStatus(entity: MediaRequest): Promise<void> {
    method handleRemoveParentUpdate (line 946) | public async handleRemoveParentUpdate(
    method afterUpdate (line 980) | public async afterUpdate(event: UpdateEvent<MediaRequest>): Promise<vo...
    method afterInsert (line 1019) | public async afterInsert(event: InsertEvent<MediaRequest>): Promise<vo...
    method afterRemove (line 1049) | public async afterRemove(event: RemoveEvent<MediaRequest>): Promise<vo...
    method listenTo (line 1060) | public listenTo(): typeof MediaRequest {

FILE: server/subscriber/MediaSubscriber.ts
  class MediaSubscriber (line 15) | class MediaSubscriber implements EntitySubscriberInterface<Media> {
    method updateChildRequestStatus (line 16) | private async updateChildRequestStatus(event: Media, is4k: boolean) {
    method updateRelatedMediaRequest (line 34) | private async updateRelatedMediaRequest(
    method beforeUpdate (line 124) | public async beforeUpdate(event: UpdateEvent<Media>): Promise<void> {
    method afterUpdate (line 155) | public async afterUpdate(event: UpdateEvent<Media>): Promise<void> {
    method listenTo (line 203) | public listenTo(): typeof Media {

FILE: server/test/db.ts
  function setupTestDb (line 4) | function setupTestDb() {

FILE: server/types/error.ts
  class ApiError (line 3) | class ApiError extends Error {
    method constructor (line 4) | constructor(

FILE: server/types/express-session.d.ts
  type SessionData (line 6) | interface SessionData {

FILE: server/types/express.d.ts
  type Request (line 8) | interface Request {
  type Middleware (line 14) | type Middleware = <ParamsDictionary, any, any>(

FILE: server/types/languages.d.ts
  type AvailableLocale (line 1) | type AvailableLocale =

FILE: server/utils/DbColumnHelper.ts
  function resolveDbType (line 8) | function resolveDbType(pgType: ColumnType): ColumnType {
  function DbAwareColumn (line 15) | function DbAwareColumn(columnOptions: ColumnOptions) {

FILE: server/utils/appDataVolume.ts
  constant CONFIG_PATH (line 4) | const CONFIG_PATH = process.env.CONFIG_DIRECTORY
  constant DOCKER_PATH (line 8) | const DOCKER_PATH = `${CONFIG_PATH}/DOCKER`;

FILE: server/utils/appVersion.ts
  constant COMMIT_TAG_PATH (line 5) | const COMMIT_TAG_PATH = path.join(__dirname, '../../committag.json');

FILE: server/utils/asyncLock.ts
  class AsyncLock (line 8) | class AsyncLock {
    method constructor (line 12) | constructor() {

FILE: server/utils/customProxyAgent.ts
  function createCustomProxyAgent (line 13) | async function createCustomProxyAgent(
  function isLocalAddress (line 124) | function isLocalAddress(hostname: string) {

FILE: server/utils/dnsCache.ts
  function initializeDnsCache (line 6) | function initializeDnsCache({

FILE: server/utils/getHostname.ts
  type HostnameParams (line 3) | interface HostnameParams {

FILE: server/utils/jellyfin.ts
  function normalizeJellyfinGuid (line 1) | function normalizeJellyfinGuid(

FILE: server/utils/restartFlag.ts
  class RestartFlag (line 4) | class RestartFlag {
    method initializeSettings (line 7) | public initializeSettings(settings: AllSettings): void {
    method isSet (line 14) | public isSet(): boolean {

FILE: server/utils/seedTestDb.ts
  type SeedDbOptions (line 6) | interface SeedDbOptions {
  constant TEST_USER_PASSWORD_HASH (line 15) | const TEST_USER_PASSWORD_HASH =
  function seedTestUsers (line 22) | async function seedTestUsers(): Promise<void> {
  function seedTestDb (line 70) | async function seedTestDb(options: SeedDbOptions = {}): Promise<void> {
  function resetTestDb (line 93) | async function resetTestDb(): Promise<void> {

FILE: src/components/AirDateBadge/index.tsx
  type AirDateBadgeProps (line 10) | type AirDateBadgeProps = {

FILE: src/components/Blocklist/index.tsx
  type Filter (line 52) | enum Filter {
  type BlocklistedItemProps (line 268) | interface BlocklistedItemProps {

FILE: src/components/BlocklistBlock/index.tsx
  type BlocklistBlockProps (line 24) | interface BlocklistBlockProps {

FILE: src/components/BlocklistModal/index.tsx
  type BlocklistModalProps (line 11) | interface BlocklistModalProps {

FILE: src/components/BlocklistedTagsBadge/index.tsx
  type BlocklistedTagsBadgeProps (line 15) | interface BlocklistedTagsBadgeProps {

FILE: src/components/BlocklistedTagsSelector/index.tsx
  type SingleVal (line 48) | type SingleVal = {
  type BlocklistedTagsSelectorProps (line 53) | type BlocklistedTagsSelectorProps = {
  type BaseSelectorMultiProps (line 107) | type BaseSelectorMultiProps = {
  type BlocklistedTagsImportButtonProps (line 184) | type BlocklistedTagsImportButtonProps = {
  type BlocklistedTagImportFormProps (line 242) | type BlocklistedTagImportFormProps = BlocklistedTagsImportButtonProps;

FILE: src/components/CollectionDetails/index.tsx
  type CollectionDetailsProps (line 32) | interface CollectionDetailsProps {

FILE: src/components/Common/Accordion/index.tsx
  type AccordionProps (line 4) | interface AccordionProps {
  type AccordionChildProps (line 12) | interface AccordionChildProps {
  type AccordionContentProps (line 18) | type AccordionContentProps = {

FILE: src/components/Common/Alert/index.tsx
  type AlertProps (line 7) | interface AlertProps {

FILE: src/components/Common/Badge/index.tsx
  type BadgeProps (line 4) | interface BadgeProps {

FILE: src/components/Common/Button/index.tsx
  type ButtonType (line 5) | type ButtonType =
  type MergeElementProps (line 14) | type MergeElementProps<
  type ElementTypes (line 19) | type ElementTypes = 'button' | 'a';
  type Element (line 21) | type Element<P extends ElementTypes = 'button'> = P extends 'a'
  type BaseProps (line 25) | type BaseProps<P> = {
  type ButtonProps (line 34) | type ButtonProps<P extends React.ElementType> = {
  function Button (line 38) | function Button<P extends ElementTypes = 'button'>(

FILE: src/components/Common/ButtonWithDropdown/index.tsx
  type ButtonWithDropdownProps (line 7) | type ButtonWithDropdownProps = {

FILE: src/components/Common/CachedImage/index.tsx
  type CachedImageProps (line 7) | type CachedImageProps = ImageProps & {

FILE: src/components/Common/ConfirmButton/index.tsx
  type ConfirmButtonProps (line 5) | interface ConfirmButtonProps {

FILE: src/components/Common/Dropdown/index.tsx
  type DropdownItemProps (line 12) | interface DropdownItemProps extends AnchorHTMLAttributes<HTMLAnchorEleme...
  type DropdownItemsProps (line 38) | type DropdownItemsProps = HTMLAttributes<HTMLDivElement> & {
  type DropdownProps (line 74) | interface DropdownProps extends ButtonHTMLAttributes<HTMLButtonElement> {

FILE: src/components/Common/Header/index.tsx
  type HeaderProps (line 1) | interface HeaderProps {

FILE: src/components/Common/ImageFader/index.tsx
  type ImageFaderProps (line 5) | interface ImageFaderProps extends HTMLAttributes<HTMLDivElement> {
  constant DEFAULT_ROTATION_SPEED (line 12) | const DEFAULT_ROTATION_SPEED = 6000;

FILE: src/components/Common/LabeledCheckbox/index.tsx
  type LabeledCheckboxProps (line 4) | interface LabeledCheckboxProps {

FILE: src/components/Common/List/index.tsx
  type ListItemProps (line 3) | interface ListItemProps {
  type ListProps (line 22) | interface ListProps {

FILE: src/components/Common/ListView/index.tsx
  type ListViewProps (line 17) | type ListViewProps = {

FILE: src/components/Common/Modal/index.tsx
  type ModalProps (line 14) | interface ModalProps {

FILE: src/components/Common/MultiRangeSlider/index.tsx
  type MultiRangeSliderProps (line 5) | type MultiRangeSliderProps = {

FILE: src/components/Common/PageTitle/index.tsx
  type PageTitleProps (line 4) | interface PageTitleProps {

FILE: src/components/Common/PlayButton/index.tsx
  type PlayButtonProps (line 3) | interface PlayButtonProps {
  type PlayButtonLink (line 7) | interface PlayButtonLink {

FILE: src/components/Common/ProgressCircle/index.tsx
  type ProgressCircleProps (line 3) | interface ProgressCircleProps {

FILE: src/components/Common/SensitiveInput/index.tsx
  type CustomInputProps (line 5) | interface CustomInputProps extends React.ComponentProps<'input'> {
  type CustomFieldProps (line 9) | interface CustomFieldProps extends React.ComponentProps<typeof Field> {
  type SensitiveInputProps (line 13) | type SensitiveInputProps = CustomInputProps | CustomFieldProps;

FILE: src/components/Common/SettingsTabs/index.tsx
  type SettingsRoute (line 7) | interface SettingsRoute {
  type SettingsLinkProps (line 17) | type SettingsLinkProps = {

FILE: src/components/Common/SlideCheckbox/index.tsx
  type SlideCheckboxProps (line 1) | type SlideCheckboxProps = {

FILE: src/components/Common/SlideOver/index.tsx
  type SlideOverProps (line 8) | interface SlideOverProps {

FILE: src/components/Common/StatusBadgeMini/index.tsx
  type StatusBadgeMiniProps (line 12) | interface StatusBadgeMiniProps {

FILE: src/components/Common/Table/index.tsx
  type TBodyProps (line 3) | type TBodyProps = {
  type TDProps (line 33) | type TDProps = {
  type TableProps (line 74) | type TableProps = {

FILE: src/components/Common/Tag/index.tsx
  type TagProps (line 4) | type TagProps = {

FILE: src/components/Common/Tooltip/index.tsx
  type TooltipProps (line 6) | type TooltipProps = {

FILE: src/components/CompanyCard/index.tsx
  type CompanyCardProps (line 5) | interface CompanyCardProps {

FILE: src/components/CompanyTag/index.tsx
  type CompanyTagProps (line 7) | type CompanyTagProps = {

FILE: src/components/Discover/CreateSlider/index.tsx
  type CreateSliderProps (line 49) | type CreateSliderProps = {
  type CreateOption (line 54) | type CreateOption = {

FILE: src/components/Discover/DiscoverSliderEdit/index.tsx
  type DiscoverSliderEditProps (line 42) | type DiscoverSliderEditProps = {
  method getItems (line 74) | getItems() {

FILE: src/components/Discover/FilterSlideover/index.tsx
  type FilterSlideoverProps (line 50) | type FilterSlideoverProps = {

FILE: src/components/Discover/NetworkSlider/index.tsx
  type Network (line 10) | interface Network {

FILE: src/components/Discover/StudioSlider/index.tsx
  type Studio (line 10) | interface Studio {

FILE: src/components/Discover/Trending.tsx
  type MediaType (line 23) | type MediaType = 'all' | 'movie' | 'tv';
  type TimeWindow (line 25) | type TimeWindow = 'day' | 'week';

FILE: src/components/Discover/constants.ts
  type AvailableColors (line 5) | type AvailableColors =
  type FilterOptions (line 120) | type FilterOptions = z.infer<typeof QueryFilterOptions>;

FILE: src/components/DownloadBlock/index.tsx
  type DownloadBlockProps (line 12) | interface DownloadBlockProps {

FILE: src/components/ExternalLinkBlock/index.tsx
  type ExternalLinkBlockProps (line 15) | interface ExternalLinkBlockProps {

FILE: src/components/GenreCard/index.tsx
  type GenreCardProps (line 6) | interface GenreCardProps {

FILE: src/components/GenreTag/index.tsx
  type GenreTagProps (line 7) | type GenreTagProps = {

FILE: src/components/IssueBlock/index.tsx
  type IssueBlockProps (line 14) | interface IssueBlockProps {

FILE: src/components/IssueDetails/IssueComment/index.tsx
  type IssueCommentProps (line 27) | interface IssueCommentProps {

FILE: src/components/IssueDetails/IssueDescription/index.tsx
  type IssueDescriptionProps (line 18) | interface IssueDescriptionProps {

FILE: src/components/IssueList/IssueItem/index.tsx
  type IssueItemProps (line 37) | interface IssueItemProps {

FILE: src/components/IssueList/index.tsx
  type Filter (line 28) | enum Filter {
  type Sort (line 34) | type Sort = 'added' | 'modified';

FILE: src/components/IssueModal/CreateIssueModal/index.tsx
  type CreateIssueModalProps (line 50) | interface CreateIssueModalProps {

FILE: src/components/IssueModal/constants.ts
  type IssueOption (line 12) | interface IssueOption {

FILE: src/components/IssueModal/index.tsx
  type IssueModalProps (line 4) | interface IssueModalProps {

FILE: src/components/JSONEditor/index.tsx
  type JSONEditorProps (line 6) | interface JSONEditorProps extends HTMLAttributes<HTMLDivElement> {

FILE: src/components/KeywordTag/index.tsx
  type KeywordTagProps (line 6) | type KeywordTagProps = {

FILE: src/components/LanguageSelector/index.tsx
  type OptionType (line 16) | type OptionType = {
  type LanguageSelectorProps (line 31) | interface LanguageSelectorProps {

FILE: src/components/Layout/MobileMenu/index.tsx
  type MobileMenuProps (line 33) | interface MobileMenuProps {
  type MenuLink (line 40) | interface MenuLink {

FILE: src/components/Layout/Sidebar/index.tsx
  type SidebarProps (line 36) | interface SidebarProps {
  type SidebarLinkProps (line 45) | interface SidebarLinkProps {

FILE: src/components/Layout/UserDropdown/MiniQuotaDisplay/index.tsx
  type MiniQuotaDisplayProps (line 17) | type MiniQuotaDisplayProps = {

FILE: src/components/Layout/UserWarnings/index.tsx
  type UserWarningsProps (line 13) | interface UserWarningsProps {

FILE: src/components/Layout/VersionStatus/index.tsx
  type VersionStatusProps (line 21) | interface VersionStatusProps {

FILE: src/components/Layout/index.tsx
  type LayoutProps (line 15) | type LayoutProps = {

FILE: src/components/LoadingBar/index.tsx
  type BarProps (line 6) | interface BarProps {

FILE: src/components/Login/AddEmailModal.tsx
  type AddEmailModalProps (line 22) | interface AddEmailModalProps {

FILE: src/components/Login/JellyfinLogin.tsx
  type JellyfinLoginProps (line 32) | interface JellyfinLoginProps {

FILE: src/components/Login/LocalLogin.tsx
  type LocalLoginProps (line 28) | interface LocalLoginProps {

FILE: src/components/Login/PlexLoginButton.tsx
  type PlexLoginButtonProps (line 13) | interface PlexLoginButtonProps {

FILE: src/components/ManageSlideOver/index.tsx
  type ManageSlideOverProps (line 84) | interface ManageSlideOverProps {
  type ManageSlideOverMovieProps (line 91) | interface ManageSlideOverMovieProps extends ManageSlideOverProps {
  type ManageSlideOverTvProps (line 96) | interface ManageSlideOverTvProps extends ManageSlideOverProps {

FILE: src/components/MediaSlider/ShowMoreCard/index.tsx
  type ShowMoreCardProps (line 14) | interface ShowMoreCardProps {

FILE: src/components/MediaSlider/index.tsx
  type MixedResult (line 19) | interface MixedResult {
  type MediaSliderProps (line 26) | interface MediaSliderProps {

FILE: src/components/MetadataSelector/index.tsx
  type MetadataProviderType (line 5) | enum MetadataProviderType {
  type MetadataProviderOptionType (line 10) | type MetadataProviderOptionType = {
  type MetadataSelectorProps (line 22) | interface MetadataSelectorProps {

FILE: src/components/MovieDetails/index.tsx
  type MovieDetailsProps (line 111) | interface MovieDetailsProps {
  function getAvailableMediaServerName (line 302) | function getAvailableMediaServerName() {
  function getAvailable4kMediaServerName (line 314) | function getAvailable4kMediaServerName() {

FILE: src/components/NotificationTypeSelector/NotificationType/index.tsx
  type NotificationTypeProps (line 4) | interface NotificationTypeProps {

FILE: src/components/NotificationTypeSelector/index.tsx
  type Notification (line 95) | enum Notification {
  constant ALL_NOTIFICATIONS (line 111) | const ALL_NOTIFICATIONS = Object.values(Notification)
  type NotificationItem (line 115) | interface NotificationItem {
  type NotificationTypeSelectorProps (line 125) | interface NotificationTypeSelectorProps {

FILE: src/components/PWAHeader/index.tsx
  type PWAHeaderProps (line 1) | interface PWAHeaderProps {

FILE: src/components/PermissionEdit/index.tsx
  type PermissionEditProps (line 90) | interface PermissionEditProps {

FILE: src/components/PermissionOption/index.tsx
  type PermissionItem (line 6) | interface PermissionItem {
  type PermissionRequirement (line 15) | interface PermissionRequirement {
  type PermissionOptionProps (line 20) | interface PermissionOptionProps {

FILE: src/components/PersonCard/index.tsx
  type PersonCardProps (line 6) | interface PersonCardProps {

FILE: src/components/PersonDetails/index.tsx
  type MediaType (line 29) | type MediaType = 'all' | 'movie' | 'tv';

FILE: src/components/QuotaSelector/index.tsx
  type QuotaSelectorProps (line 16) | interface QuotaSelectorProps {

FILE: src/components/RegionSelector/index.tsx
  type RegionSelectorProps (line 18) | interface RegionSelectorProps {

FILE: src/components/RequestBlock/index.tsx
  type RequestBlockProps (line 43) | interface RequestBlockProps {

FILE: src/components/RequestButton/index.tsx
  type ButtonOption (line 40) | interface ButtonOption {
  type RequestButtonProps (line 47) | interface RequestButtonProps {

FILE: src/components/RequestCard/index.tsx
  type RequestCardErrorProps (line 61) | interface RequestCardErrorProps {
  type RequestCardProps (line 215) | interface RequestCardProps {

FILE: src/components/RequestList/RequestItem/index.tsx
  type RequestItemErrorProps (line 55) | interface RequestItemErrorProps {
  type RequestItemProps (line 291) | interface RequestItemProps {

FILE: src/components/RequestList/index.tsx
  type Filter (line 38) | enum Filter {
  type Sort (line 50) | type Sort = 'added' | 'modified';
  type SortDirection (line 52) | type SortDirection = 'asc' | 'desc';
  type MediaType (line 54) | type MediaType = 'all' | 'movie' | 'tv';

FILE: src/components/RequestModal/AdvancedRequester/index.tsx
  type OptionType (line 23) | type OptionType = {
  type RequestOverrides (line 43) | type RequestOverrides = {
  type AdvancedRequesterProps (line 52) | interface AdvancedRequesterProps {

FILE: src/components/RequestModal/CollectionRequestModal.tsx
  type RequestModalProps (line 34) | interface RequestModalProps extends React.HTMLAttributes<HTMLDivElement> {

FILE: src/components/RequestModal/MovieRequestModal.tsx
  type RequestModalProps (line 40) | interface RequestModalProps extends React.HTMLAttributes<HTMLDivElement> {

FILE: src/components/RequestModal/QuotaDisplay/index.tsx
  type QuotaDisplayProps (line 31) | interface QuotaDisplayProps {

FILE: src/components/RequestModal/SearchByNameModal/index.tsx
  type SearchByNameModalProps (line 16) | interface SearchByNameModalProps {

FILE: src/components/RequestModal/TvRequestModal.tsx
  type RequestModalProps (line 55) | interface RequestModalProps extends React.HTMLAttributes<HTMLDivElement> {

FILE: src/components/RequestModal/index.tsx
  type RequestModalProps (line 9) | interface RequestModalProps {

FILE: src/components/Selector/CertificationSelector.tsx
  type Certification (line 9) | interface Certification {
  type CertificationResponse (line 15) | interface CertificationResponse {
  type CertificationOption (line 21) | interface CertificationOption {
  type CertificationSelectorProps (line 27) | interface CertificationSelectorProps {

FILE: src/components/Selector/USCertificationSelector.tsx
  type USCertificationSelectorProps (line 3) | interface USCertificationSelectorProps {
  constant US_MOVIE_CERTIFICATIONS (line 12) | const US_MOVIE_CERTIFICATIONS = ['NR', 'G', 'PG', 'PG-13', 'R', 'NC-17'];
  constant US_TV_CERTIFICATIONS (line 13) | const US_TV_CERTIFICATIONS = [

FILE: src/components/Selector/index.tsx
  type SingleVal (line 47) | type SingleVal = {
  type BaseSelectorMultiProps (line 52) | type BaseSelectorMultiProps = {
  type BaseSelectorSingleProps (line 59) | type BaseSelectorSingleProps = {
  type GenreSelectorProps (line 146) | type GenreSelectorProps = (BaseSelectorMultiProps | BaseSelectorSinglePr...
  type WatchProviderSelectorProps (line 371) | type WatchProviderSelectorProps = {

FILE: src/components/Settings/CopyButton.tsx
  type CopyButtonProps (line 8) | type CopyButtonProps = {

FILE: src/components/Settings/LibraryItem.tsx
  type LibraryItemProps (line 3) | interface LibraryItemProps {

FILE: src/components/Settings/Notifications/NotificationsEmail.tsx
  function OpenPgpLink (line 53) | function OpenPgpLink(msg: React.ReactNode) {

FILE: src/components/Settings/OverrideRule/OverrideRuleModal.tsx
  type OptionType (line 50) | type OptionType = {
  type OverrideRuleModalProps (line 55) | interface OverrideRuleModalProps {

FILE: src/components/Settings/OverrideRule/OverrideRuleTiles.tsx
  type OverrideRuleTilesProps (line 32) | interface OverrideRuleTilesProps {

FILE: src/components/Settings/RadarrModal/index.tsx
  type OptionType (line 17) | type OptionType = {
  type RadarrModalProps (line 77) | interface RadarrModalProps {

FILE: src/components/Settings/SettingsAbout/Releases/index.tsx
  constant REPO_RELEASE_API (line 31) | const REPO_RELEASE_API =
  type GitHubRelease (line 34) | interface GitHubRelease {
  type ReleaseProps (line 53) | interface ReleaseProps {
  type ReleasesProps (line 123) | interface ReleasesProps {

FILE: src/components/Settings/SettingsJellyfin.tsx
  type Library (line 72) | interface Library {
  type SyncStatus (line 78) | interface SyncStatus {
  type SettingsJellyfinProps (line 86) | interface SettingsJellyfinProps {

FILE: src/components/Settings/SettingsJobsCache/index.tsx
  type Job (line 115) | interface Job {
  type JobModalState (line 125) | type JobModalState = {
  type JobModalAction (line 134) | type JobModalAction =

FILE: src/components/Settings/SettingsLayout.tsx
  type SettingsLayoutProps (line 24) | type SettingsLayoutProps = {
  function getAvailableMediaServerName (line 99) | function getAvailableMediaServerName() {

FILE: src/components/Settings/SettingsLogs/index.tsx
  type Filter (line 57) | type Filter = 'debug' | 'info' | 'warn' | 'error';

FILE: src/components/Settings/SettingsMetadata.tsx
  type ProviderStatus (line 44) | type ProviderStatus = 'ok' | 'not tested' | 'failed';
  type ProviderResponse (line 46) | interface ProviderResponse {
  type MetadataValues (line 51) | interface MetadataValues {
  type MetadataSettings (line 56) | interface MetadataSettings {

FILE: src/components/Settings/SettingsNotifications.tsx
  type SettingsNotificationsProps (line 26) | type SettingsNotificationsProps = {

FILE: src/components/Settings/SettingsPlex.tsx
  type Library (line 86) | interface Library {
  type SyncStatus (line 92) | interface SyncStatus {
  type PresetServerDisplay (line 100) | interface PresetServerDisplay {
  type SettingsPlexProps (line 110) | interface SettingsPlexProps {

FILE: src/components/Settings/SettingsServices.tsx
  type ServerInstanceProps (line 55) | interface ServerInstanceProps {
  type DVRTestResponse (line 69) | interface DVRTestResponse {
  type RadarrTestResponse (line 85) | type RadarrTestResponse = DVRTestResponse;
  type SonarrTestResponse (line 87) | type SonarrTestResponse = DVRTestResponse & {

FILE: src/components/Settings/SonarrModal/index.tsx
  type OptionType (line 18) | type OptionType = {
  type SonarrModalProps (line 84) | interface SonarrModalProps {

FILE: src/components/Setup/JellyfinSetup.tsx
  type JellyfinSetupProps (line 49) | interface JellyfinSetupProps {
  function JellyfinSetup (line 55) | function JellyfinSetup({

FILE: src/components/Setup/LoginWithPlex.tsx
  type LoginWithPlexProps (line 13) | interface LoginWithPlexProps {

FILE: src/components/Setup/SetupLogin.tsx
  type LoginWithMediaServerProps (line 21) | interface LoginWithMediaServerProps {

FILE: src/components/Setup/SetupSteps.tsx
  type CurrentStep (line 3) | interface CurrentStep {

FILE: src/components/Slider/index.tsx
  type SliderProps (line 9) | interface SliderProps {
  type Direction (line 18) | enum Direction {

FILE: src/components/StatusBadge/index.tsx
  type StatusBadgeProps (line 24) | interface StatusBadgeProps {

FILE: src/components/TitleCard/ErrorCard.tsx
  type ErrorCardProps (line 9) | interface ErrorCardProps {

FILE: src/components/TitleCard/Placeholder.tsx
  type PlaceholderProps (line 1) | interface PlaceholderProps {

FILE: src/components/TitleCard/TmdbTitleCard.tsx
  type TmdbTitleCardProps (line 8) | interface TmdbTitleCardProps {

FILE: src/components/TitleCard/index.tsx
  type TitleCardProps (line 33) | interface TitleCardProps {

FILE: src/components/TvDetails/Season/index.tsx
  type SeasonProps (line 14) | type SeasonProps = {

FILE: src/components/TvDetails/index.tsx
  type TvDetailsProps (line 110) | interface TvDetailsProps {
  function getAvailableMediaServerName (line 330) | function getAvailableMediaServerName() {
  function getAvailable4kMediaServerName (line 342) | function getAvailable4kMediaServerName() {

FILE: src/components/UserList/BulkEditModal.tsx
  type BulkEditProps (line 13) | interface BulkEditProps {

FILE: src/components/UserList/JellyfinImportModal.tsx
  type JellyfinImportProps (line 15) | interface JellyfinImportProps {

FILE: src/components/UserList/PlexImportModal.tsx
  type PlexImportProps (line 13) | interface PlexImportProps {

FILE: src/components/UserList/index.tsx
  type Sort (line 88) | type Sort = 'created' | 'updated' | 'requests' | 'displayname';

FILE: src/components/UserProfile/ProfileHeader/index.tsx
  type ProfileHeaderProps (line 17) | interface ProfileHeaderProps {

FILE: src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/LinkJellyfinModal.tsx
  type LinkJellyfinModalProps (line 33) | interface LinkJellyfinModalProps {

FILE: src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx
  type LinkedAccountType (line 41) | enum LinkedAccountType {
  type LinkedAccount (line 47) | type LinkedAccount = {

FILE: src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush/DeviceItem.tsx
  type DeviceItemProps (line 14) | interface DeviceItemProps {

FILE: src/components/UserProfile/UserSettings/UserNotificationSettings/index.tsx
  type UserNotificationSettingsProps (line 29) | type UserNotificationSettingsProps = {

FILE: src/components/UserProfile/UserSettings/index.tsx
  type UserSettingsProps (line 28) | type UserSettingsProps = {

FILE: src/components/UserProfile/index.tsx
  type MediaTitle (line 43) | type MediaTitle = MovieDetails | TvDetails;

FILE: src/context/InteractionContext.tsx
  type InteractionContextProps (line 4) | interface InteractionContextProps {

FILE: src/context/LanguageContext.tsx
  type AvailableLanguageObject (line 4) | type AvailableLanguageObject = Record<
  type LanguageContextProps (line 156) | interface LanguageContextProps {

FILE: src/context/SettingsContext.tsx
  type SettingsContextProps (line 6) | interface SettingsContextProps {

FILE: src/context/UserContext.tsx
  type UserContextProps (line 6) | interface UserContextProps {

FILE: src/hooks/useDeepLinks.ts
  type useDeepLinksProps (line 5) | interface useDeepLinksProps {

FILE: src/hooks/useDiscover.ts
  type BaseSearchResult (line 10) | interface BaseSearchResult<T> {
  type BaseMedia (line 17) | interface BaseMedia {
  type DiscoverResult (line 25) | interface DiscoverResult<T, S> {

FILE: src/hooks/useInteraction.ts
  constant INTERACTION_TYPE (line 3) | const INTERACTION_TYPE = {
  constant UPDATE_INTERVAL (line 9) | const UPDATE_INTERVAL = 1000;

FILE: src/hooks/usePlexLogin.ts
  function usePlexLogin (line 6) | function usePlexLogin({

FILE: src/hooks/useRequestOverride.ts
  type OverrideStatus (line 8) | interface OverrideStatus {

FILE: src/hooks/useSearchInput.ts
  type Url (line 9) | type Url = string | UrlObject;
  type SearchObject (line 11) | interface SearchObject {

FILE: src/hooks/useUpdateQueryParams.ts
  type UseQueryParamReturnedFunction (line 6) | type UseQueryParamReturnedFunction = (
  type MergedQueryString (line 11) | interface MergedQueryString {

FILE: src/hooks/useUser.ts
  type User (line 12) | interface User {
  type NotificationAgentTypes (line 29) | type NotificationAgentTypes = Record<NotificationAgentKey, number>;
  type UserSettings (line 31) | interface UserSettings {
  type UserHookResponse (line 42) | interface UserHookResponse {

FILE: src/hooks/useVerticalScroll.ts
  constant IS_SCROLLING_CHECK_THROTTLE (line 5) | const IS_SCROLLING_CHECK_THROTTLE = 200;
  constant BUFFER_HEIGHT (line 6) | const BUFFER_HEIGHT = 200;
  type SetTimeoutReturnType (line 20) | type SetTimeoutReturnType = ReturnType<typeof setTimeout>;

FILE: src/i18n/extractMessages.ts
  function getFiles (line 5) | async function getFiles(dir: string): Promise<string[]> {
  function extractMessages (line 17) | async function extractMessages(
  function processMessages (line 46) | async function processMessages(dir: string): Promise<string> {
  function saveMessages (line 102) | async function saveMessages() {

FILE: src/pages/_app.tsx
  type NextAppComponentType (line 110) | type NextAppComponentType = typeof App;
  type MessagesType (line 111) | type MessagesType = Record<string, string>;
  type ExtendedAppProps (line 113) | interface ExtendedAppProps extends AppProps {

FILE: src/pages/_document.tsx
  class MyDocument (line 4) | class MyDocument extends Document {
    method getInitialProps (line 5) | static async getInitialProps(
    method render (line 13) | render(): JSX.Element {

FILE: src/pages/_error.tsx
  type ErrorProps (line 9) | interface ErrorProps {

FILE: src/pages/collection/[collectionId]/index.tsx
  type CollectionPageProps (line 6) | interface CollectionPageProps {

FILE: src/pages/movie/[movieId]/index.tsx
  type MoviePageProps (line 6) | interface MoviePageProps {

FILE: src/pages/tv/[tvId]/index.tsx
  type TvPageProps (line 6) | interface TvPageProps {

FILE: src/types/custom.d.ts
  type IClassNames (line 27) | interface IClassNames {

FILE: src/types/react-intl-auto.d.ts
  type ExtractableMessage (line 4) | interface ExtractableMessage {

FILE: src/utils/defineMessages.ts
  type Messages (line 3) | type Messages<T extends Record<string, string>> = {
  function defineMessages (line 10) | function defineMessages<T extends Record<string, string>>(

FILE: src/utils/jellyfin.ts
  type JellyfinAuthenticationResult (line 4) | interface JellyfinAuthenticationResult {
  class JellyAPI (line 10) | class JellyAPI {
    method login (line 11) | public login(

FILE: src/utils/plex.ts
  type PlexHeaders (line 4) | interface PlexHeaders extends Record<string, string> {
  type PlexPin (line 18) | interface PlexPin {
  class PlexOAuth (line 36) | class PlexOAuth {
    method initializeHeaders (line 44) | public initializeHeaders(): void {
    method getPin (line 75) | public async getPin(): Promise<PlexPin> {
    method preparePopup (line 92) | public preparePopup(): void {
    method login (line 96) | public async login(): Promise<string> {
    method pinPoll (line 129) | private async pinPoll(): Promise<string> {
    method closePopup (line 162) | private closePopup(): void {
    method openPopup (line 167) | private openPopup({
    method encodeData (line 219) | private encodeData(data: Record<string, string>): string {

FILE: src/utils/pushSubscriptionHelpers.ts
  function urlBase64ToUint8Array (line 6) | function urlBase64ToUint8Array(base64String: string) {

FILE: src/utils/typeHelpers.ts
  type Undefinable (line 1) | type Undefinable<T> = T | undefined;
  type Nullable (line 2) | type Nullable<T> = T | null;
  type Maybe (line 3) | type Maybe<T> = T | null | undefined;
  function withProperties (line 12) | function withProperties<A extends object, B extends object>(

FILE: src/utils/urlValidationHelper.ts
  function isValidURL (line 1) | function isValidURL(value: unknown) {
Condensed preview — 787 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (8,809K chars).
[
  {
    "path": ".dockerignore",
    "chars": 329,
    "preview": "**/*.md\n**/.gitkeep\n**/.vscode\n.dockerignore\n.editorconfig\n.eslintrc.js\n.git\n.gitconfig\n.github\n.gitignore\n.husky\n.next\n"
  },
  {
    "path": ".editorconfig",
    "chars": 207,
    "preview": "# editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\ni"
  },
  {
    "path": ".gitattributes",
    "chars": 750,
    "preview": "* text eol=lf\n\n#\n## These files are binary and should be left untouched\n#\n\n# (binary is a macro for -text -diff)\n*.png b"
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 79,
    "preview": "# Global code ownership\n*                               @seerr-team/seerr-core\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 23,
    "preview": "open_collective: seerr\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.yml",
    "chars": 3257,
    "preview": "name: 🐛 Bug Report\ndescription: Report a problem\nlabels: ['awaiting triage']\ntype: bug\nbody:\n  - type: markdown\n    attr"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 331,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: 💬 Support via Discord\n    url: https://discord.gg/seerr\n    about: "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/documentation.yml",
    "chars": 1989,
    "preview": "name: 📚 Documentation\ndescription: Report a docs problem or suggest a docs improvement\ntitle: \"[Docs]: \"\nlabels: [\"docum"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/enhancement.yml",
    "chars": 1727,
    "preview": "name: ✨ Feature Request\ndescription: Suggest an idea\nlabels: ['awaiting triage']\ntype: feature\nbody:\n  - type: markdown\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/maintenance.yml",
    "chars": 1934,
    "preview": "name: 🧰 Maintenance / Chore\ndescription: CI, GitHub Actions, build, dependencies, refactors (non-feature work)\ntitle: \"["
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 1337,
    "preview": "<!--\n    Please read contributing guide before submitting\n    your pull request. Please fill in each section below to he"
  },
  {
    "path": ".github/cliff.toml",
    "chars": 3264,
    "preview": "# git-cliff ~ configuration\n# https://git-cliff.org/docs/configuration\n\n[changelog]\nheader = \"\"\nbody = \"\"\"\n{%- macro rem"
  },
  {
    "path": ".github/renovate/actions.json5",
    "chars": 274,
    "preview": "{\n  $schema: 'https://docs.renovatebot.com/renovate-schema.json',\n\n  extends: ['helpers:pinGitHubActionDigests'],\n\n  pac"
  },
  {
    "path": ".github/renovate/docker.json5",
    "chars": 240,
    "preview": "{\n  $schema: 'https://docs.renovatebot.com/renovate-schema.json',\n\n  extends: [\n    'docker:enableMajor',\n    'docker:pi"
  },
  {
    "path": ".github/renovate/groups.json5",
    "chars": 387,
    "preview": "{\n  $schema: 'https://docs.renovatebot.com/renovate-schema.json',\n\n  packageRules: [\n    // Node.js\n    {\n      matchPac"
  },
  {
    "path": ".github/renovate/helm.json5",
    "chars": 588,
    "preview": "{\n  $schema: 'https://docs.renovatebot.com/renovate-schema.json',\n\n  packageRules: [\n    {\n      matchManagers: ['helm-v"
  },
  {
    "path": ".github/renovate/labels.json5",
    "chars": 530,
    "preview": "{\n  $schema: 'https://docs.renovatebot.com/renovate-schema.json',\n\n  packageRules: [\n    // JavaScript/npm packages\n    "
  },
  {
    "path": ".github/renovate/pnpm.json5",
    "chars": 203,
    "preview": "{\n  $schema: 'https://docs.renovatebot.com/renovate-schema.json',\n\n  // Run pnpm dedupe after dependency updates\n  postU"
  },
  {
    "path": ".github/renovate/semanticCommits.json5",
    "chars": 672,
    "preview": "{\n  $schema: 'https://docs.renovatebot.com/renovate-schema.json',\n\n  packageRules: [\n    // Default for all dependencies"
  },
  {
    "path": ".github/renovate.json5",
    "chars": 968,
    "preview": "{\n  $schema: 'https://docs.renovatebot.com/renovate-schema.json',\n  extends: [\n    'config:recommended',\n    ':dependenc"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 11045,
    "preview": "---\n# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\nname: Seerr CI\n\non:\n  pull_request"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "chars": 1441,
    "preview": "---\n# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\nname: 'CodeQL'\n\non:\n  push:\n    br"
  },
  {
    "path": ".github/workflows/conflict_labeler.yml",
    "chars": 940,
    "preview": "---\n# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\nname: Merge Conflict Labeler\n\non:\n"
  },
  {
    "path": ".github/workflows/create-tag.yml",
    "chars": 2647,
    "preview": "---\n# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\nname: Create tag\n\non:\n  workflow_d"
  },
  {
    "path": ".github/workflows/cypress.yml",
    "chars": 2487,
    "preview": "---\n# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\nname: Cypress Tests\n\non:\n  pull_re"
  },
  {
    "path": ".github/workflows/detect-duplicate.yml",
    "chars": 2587,
    "preview": "# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\nname: Duplicate Issue Detector\n\non:\n  "
  },
  {
    "path": ".github/workflows/docs-deploy.yml",
    "chars": 2155,
    "preview": "---\n# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\nname: Deploy to GitHub Pages\n\non:\n"
  },
  {
    "path": ".github/workflows/docs-link-check.yml",
    "chars": 1859,
    "preview": "---\n# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\nname: Check Docs Links\n\non:\n  pull"
  },
  {
    "path": ".github/workflows/helm.yml",
    "chars": 6838,
    "preview": "---\n# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\nname: Release Charts\n\non:\n  push:\n"
  },
  {
    "path": ".github/workflows/lint-helm-charts.yml",
    "chars": 1664,
    "preview": "---\n# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\nname: Lint and Test Charts\n\non:\n  "
  },
  {
    "path": ".github/workflows/preview.yml",
    "chars": 4623,
    "preview": "---\n# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\nname: Seerr Preview\n\non:\n  push:\n "
  },
  {
    "path": ".github/workflows/rebuild-issue-index.yml",
    "chars": 1904,
    "preview": "# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\nname: Rebuild Issue Index\n\non:\n  sched"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 10552,
    "preview": "---\n# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\nname: Seerr Release\n\non:\n  push:\n "
  },
  {
    "path": ".github/workflows/renovate-helm-custom-hooks.yml",
    "chars": 7711,
    "preview": "---\n# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\nname: Renovate Helm Hooks\n\non:\n  p"
  },
  {
    "path": ".github/workflows/seerr-labeller.yml",
    "chars": 5203,
    "preview": "---\n# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\nname: 'Seerr Labeller'\n\non:\n  pull"
  },
  {
    "path": ".github/workflows/semantic-pr.yml",
    "chars": 598,
    "preview": "name: \"Semantic PR\"\n\non:\n  pull_request_target:\n    types:\n      - opened\n      - reopened\n      - edited\n      - synchr"
  },
  {
    "path": ".github/workflows/stale.yml",
    "chars": 1484,
    "preview": "---\n# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\nname: Close Stale Issues and PRs\n\n"
  },
  {
    "path": ".github/workflows/test-docs-deploy.yml",
    "chars": 1624,
    "preview": "---\n# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\nname: Test Docs deployment\n\non:\n  "
  },
  {
    "path": ".github/workflows/trivy-scan.yml",
    "chars": 1645,
    "preview": "---\n# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\nname: Trivy Container Vulnerabilit"
  },
  {
    "path": ".gitignore",
    "chars": 864,
    "preview": "# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\nlcov.info\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n"
  },
  {
    "path": ".husky/commit-msg",
    "chars": 93,
    "preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\n[ -n \"$HUSKY_BYPASS\" ] || npx commitlint --edit $1\n"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 58,
    "preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n"
  },
  {
    "path": ".husky/prepare-commit-msg",
    "chars": 83,
    "preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nexec < /dev/tty && npx cz --hook || true\n"
  },
  {
    "path": ".npmrc",
    "chars": 19,
    "preview": "engine-strict=true\n"
  },
  {
    "path": ".prettierignore",
    "chars": 291,
    "preview": "# Generated files which we would not like to format\n.next/\ndist/\nconfig/\ncache/config.json\npnpm-lock.yaml\ncypress/config"
  },
  {
    "path": ".prettierrc.js",
    "chars": 831,
    "preview": "module.exports = {\n  plugins: ['prettier-plugin-organize-imports', 'prettier-plugin-tailwindcss'],\n  singleQuote: true,\n"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 691,
    "preview": "{\n  // see\n  //  - https://code.visualstudio.com/docs/editor/extension-gallery#_workspace-recommended-extensions\n  \"reco"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 874,
    "preview": "{\n  \"eslint.enable\": true,\n  \"eslint.validate\": [\n    \"javascript\",\n    \"javascriptreact\",\n    \"typescript\",\n    \"typesc"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 5465,
    "preview": "# Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 9327,
    "preview": "# Contributing to Seerr\n\nAll help is welcome and greatly appreciated! If you would like to contribute to the project, th"
  },
  {
    "path": "Dockerfile",
    "chars": 1426,
    "preview": "FROM node:22.22.0-alpine3.22@sha256:7aa86fa052f6e4b101557ccb56717cb4311be1334381f526fe013418fe157384 AS base\nARG SOURCE_"
  },
  {
    "path": "Dockerfile.local",
    "chars": 230,
    "preview": "FROM node:22.22.0-alpine3.22@sha256:7aa86fa052f6e4b101557ccb56717cb4311be1334381f526fe013418fe157384\n\nENV PNPM_HOME=\"/pn"
  },
  {
    "path": "LICENSE",
    "chars": 1060,
    "preview": "MIT License\n\nCopyright (c) 2020 sct\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof thi"
  },
  {
    "path": "README.md",
    "chars": 4179,
    "preview": "<p align=\"center\">\n<img src=\"./public/logo_full.svg\" alt=\"Seerr\" style=\"margin: 20px 0;\">\n</p>\n<p align=\"center\">\n<img s"
  },
  {
    "path": "SECURITY.md",
    "chars": 4492,
    "preview": "# Security Policy\n\n## Reporting Security Issues\n\nMaintainers and community take security bugs seriously. We appreciate y"
  },
  {
    "path": "bin/check-i18n.js",
    "chars": 939,
    "preview": "#!/usr/bin/env node\n\n/**\n * Check that i18n locale files are in sync with extracted messages.\n * Runs `pnpm i18n:extract"
  },
  {
    "path": "bin/duplicate-detector/.gitignore",
    "chars": 14,
    "preview": "node_modules/\n"
  },
  {
    "path": "bin/duplicate-detector/build-index.mjs",
    "chars": 3443,
    "preview": "#!/usr/bin/env node\n/**\n * Build Issue Embedding Index\n *\n * Fetches all open issues and recently closed ones,\n * genera"
  },
  {
    "path": "bin/duplicate-detector/detect.mjs",
    "chars": 7545,
    "preview": "#!/usr/bin/env node\n/**\n * Duplicate Issue Detector\n *\n * Triggered on new issue creation. Compares the new issue agains"
  },
  {
    "path": "bin/duplicate-detector/package.json",
    "chars": 335,
    "preview": "{\n  \"name\": \"duplicate-detector\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"packageManager\": \"pnpm"
  },
  {
    "path": "bin/duplicate-detector/utils.mjs",
    "chars": 2931,
    "preview": "const GITHUB_API = 'https://api.github.com';\nconst GITHUB_TOKEN = process.env.GITHUB_TOKEN;\nconst GITHUB_REPOSITORY = pr"
  },
  {
    "path": "bin/prepare.js",
    "chars": 156,
    "preview": "#!/usr/bin/env node\n\n/**\n * Do not run husky in CI environments\n */\nconst isCi = process.env.CI !== undefined;\nif (!isCi"
  },
  {
    "path": "charts/seerr-chart/.helmignore",
    "chars": 372,
    "preview": "# Patterns to ignore when building packages.\n# This supports shell glob matching, relative path matching, and\n# negation"
  },
  {
    "path": "charts/seerr-chart/Chart.yaml",
    "chars": 400,
    "preview": "apiVersion: v2\nkubeVersion: '>=1.23.0-0'\nname: seerr-chart\ndescription: Seerr helm chart for Kubernetes\ntype: applicatio"
  },
  {
    "path": "charts/seerr-chart/README.md",
    "chars": 5830,
    "preview": "# seerr-chart\n\n![Version: 3.3.0](https://img.shields.io/badge/Version-3.3.0-informational?style=flat-square) ![Type: app"
  },
  {
    "path": "charts/seerr-chart/README.md.gotmpl",
    "chars": 978,
    "preview": "{{ template \"chart.header\" . }}\n\n{{ template \"chart.deprecationWarning\" . }}\n\n{{ template \"chart.badgesSection\" . }}\n\n{{"
  },
  {
    "path": "charts/seerr-chart/artifacthub-repo.yml",
    "chars": 51,
    "preview": "repositoryID: 249547ec-2a30-48de-a5bc-07bfd5aa2e8f\n"
  },
  {
    "path": "charts/seerr-chart/templates/NOTES.txt",
    "chars": 250,
    "preview": "***********************************************************************\n Welcome to {{ .Chart.Name }}\n Chart version: {{"
  },
  {
    "path": "charts/seerr-chart/templates/_helpers.tpl",
    "chars": 2012,
    "preview": "{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"seerr.name\" -}}\n{{- default .Chart.Name .Values.nameOverride | trunc"
  },
  {
    "path": "charts/seerr-chart/templates/ingress.yaml",
    "chars": 1069,
    "preview": "{{- if .Values.ingress.enabled -}}\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: {{ include \"seerr.fu"
  },
  {
    "path": "charts/seerr-chart/templates/persistentvolumeclaim.yaml",
    "chars": 802,
    "preview": "{{- if not .Values.config.persistence.existingClaim -}}\napiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  name: {{ "
  },
  {
    "path": "charts/seerr-chart/templates/route.yaml",
    "chars": 1347,
    "preview": "{{- range $name, $route := .Values.route }}\n{{- if $route.enabled }}\n---\napiVersion: {{ $route.apiVersion | default \"gat"
  },
  {
    "path": "charts/seerr-chart/templates/service.yaml",
    "chars": 389,
    "preview": "apiVersion: v1\nkind: Service\nmetadata:\n  name: {{ include \"seerr.fullname\" . }}\n  labels:\n    {{- include \"seerr.labels\""
  },
  {
    "path": "charts/seerr-chart/templates/serviceaccount.yaml",
    "chars": 385,
    "preview": "{{- if .Values.serviceAccount.create -}}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ include \"seerr.service"
  },
  {
    "path": "charts/seerr-chart/templates/statefulset.yaml",
    "chars": 4802,
    "preview": "apiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: {{ include \"seerr.fullname\" . }}\n  labels:\n    {{- include \"seer"
  },
  {
    "path": "charts/seerr-chart/templates/tests/test-connection.yaml",
    "chars": 373,
    "preview": "apiVersion: v1\nkind: Pod\nmetadata:\n  name: \"{{ include \"seerr.fullname\" . }}-test-connection\"\n  labels:\n    {{- include "
  },
  {
    "path": "charts/seerr-chart/values.yaml",
    "chars": 4304,
    "preview": "image:\n  registry: ghcr.io\n  repository: seerr-team/seerr\n  pullPolicy: IfNotPresent\n  # -- Overrides the image tag whos"
  },
  {
    "path": "compose.postgres.yaml",
    "chars": 1104,
    "preview": "services:\n  seerr:\n    build:\n      context: .\n      dockerfile: Dockerfile.local\n    ports:\n      - '5055:5055'\n    env"
  },
  {
    "path": "compose.yaml",
    "chars": 194,
    "preview": "services:\n  seerr:\n    build:\n      context: .\n      dockerfile: Dockerfile.local\n    ports:\n      - 5055:5055\n    volum"
  },
  {
    "path": "config/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "config/db/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "config/logs/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "cypress/config/settings.cypress.json",
    "chars": 6121,
    "preview": "{\n  \"clientId\": \"6919275e-142a-48d8-be6b-93594cbd4626\",\n  \"vapidPrivate\": \"tmnslaO8ZWN6bNbSEv_rolPeBTlNxOwCCAHrM9oZz3M\","
  },
  {
    "path": "cypress/e2e/discover.cy.ts",
    "chars": 6450,
    "preview": "const clickFirstTitleCardInSlider = (sliderTitle: string): void => {\n  cy.contains('.slider-header', sliderTitle)\n    .n"
  },
  {
    "path": "cypress/e2e/login.cy.ts",
    "chars": 289,
    "preview": "describe('Login Page', () => {\n  it('succesfully logs in as an admin', () => {\n    cy.loginAsAdmin();\n    cy.visit('/');"
  },
  {
    "path": "cypress/e2e/movie-details.cy.ts",
    "chars": 286,
    "preview": "describe('Movie Details', () => {\n  it('loads a movie page', () => {\n    cy.loginAsAdmin();\n    // Try to load minions: "
  },
  {
    "path": "cypress/e2e/providers/tvdb.cy.ts",
    "chars": 4859,
    "preview": "describe('TVDB Integration', () => {\n  // Constants for routes and selectors\n  const ROUTES = {\n    home: '/',\n    metad"
  },
  {
    "path": "cypress/e2e/pull-to-refresh.cy.ts",
    "chars": 570,
    "preview": "describe('Pull To Refresh', () => {\n  beforeEach(() => {\n    cy.login(Cypress.env('ADMIN_EMAIL'), Cypress.env('ADMIN_PAS"
  },
  {
    "path": "cypress/e2e/settings/discover-customization.cy.ts",
    "chars": 4803,
    "preview": "describe('Discover Customization', () => {\n  beforeEach(() => {\n    cy.loginAsAdmin();\n    cy.intercept('/api/v1/setting"
  },
  {
    "path": "cypress/e2e/settings/general-settings.cy.ts",
    "chars": 942,
    "preview": "describe('General Settings', () => {\n  beforeEach(() => {\n    cy.loginAsAdmin();\n  });\n\n  it('opens the settings page fr"
  },
  {
    "path": "cypress/e2e/tv-details.cy.ts",
    "chars": 674,
    "preview": "describe('TV Details', () => {\n  it('loads a tv details page', () => {\n    cy.loginAsAdmin();\n    // Try to load strange"
  },
  {
    "path": "cypress/e2e/user/auto-request-settings.cy.ts",
    "chars": 2229,
    "preview": "const visitUserEditPage = (email: string): void => {\n  cy.visit('/users');\n\n  cy.contains('[data-testid=user-list-row]',"
  },
  {
    "path": "cypress/e2e/user/profile.cy.ts",
    "chars": 1399,
    "preview": "describe('User Profile', () => {\n  beforeEach(() => {\n    cy.loginAsAdmin();\n  });\n\n  it('opens user profile page from t"
  },
  {
    "path": "cypress/e2e/user/user-list.cy.ts",
    "chars": 1974,
    "preview": "const testUser = {\n  username: 'Test User',\n  emailAddress: 'test@seeerr.dev',\n  password: 'test1234',\n};\n\ndescribe('Use"
  },
  {
    "path": "cypress/fixtures/watchlist.json",
    "chars": 499,
    "preview": "{\n  \"page\": 1,\n  \"totalPages\": 1,\n  \"totalResults\": 3,\n  \"results\": [\n    {\n      \"ratingKey\": \"5d776be17a53e9001e732ab9"
  },
  {
    "path": "cypress/support/commands.ts",
    "chars": 853,
    "preview": "/// <reference types=\"cypress\" />\nimport 'cy-mobile-commands';\n\nCypress.Commands.add('login', (email, password) => {\n  c"
  },
  {
    "path": "cypress/support/e2e.ts",
    "chars": 120,
    "preview": "import './commands';\n\nbefore(() => {\n  if (Cypress.env('SEED_DATABASE')) {\n    cy.exec('pnpm cypress:prepare');\n  }\n});\n"
  },
  {
    "path": "cypress/support/index.ts",
    "chars": 328,
    "preview": "/* eslint-disable @typescript-eslint/no-namespace */\n/// <reference types=\"cypress\" />\n\ndeclare global {\n  namespace Cyp"
  },
  {
    "path": "cypress/tsconfig.json",
    "chars": 198,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\"es5\", \"dom\"],\n    \"types\": [\"cypress\", \"node\"],\n    \"resolveJ"
  },
  {
    "path": "cypress.config.ts",
    "chars": 367,
    "preview": "import { defineConfig } from 'cypress';\n\nexport default defineConfig({\n  projectId: 'onnqy3',\n  e2e: {\n    baseUrl: 'htt"
  },
  {
    "path": "docs/README.md",
    "chars": 2231,
    "preview": "---\nslug: /\nsidebar_position: 1\n---\n\n# Introduction\n\nWelcome to the Seerr Documentation.\n\n**Seerr** is a free and open s"
  },
  {
    "path": "docs/extending-seerr/_category_.json",
    "chars": 179,
    "preview": "{\n  \"label\": \"Extending Seerr\",\n  \"position\": 3,\n  \"link\": {\n    \"type\": \"generated-index\",\n    \"title\": \"Extending Seer"
  },
  {
    "path": "docs/extending-seerr/database-config.mdx",
    "chars": 7687,
    "preview": "---\ntitle: Configuring the Database (Advanced)\ndescription: Configure the database for Seerr\nsidebar_position: 2\n---\n# C"
  },
  {
    "path": "docs/extending-seerr/reverse-proxy.mdx",
    "chars": 9026,
    "preview": "---\ntitle: Reverse Proxy\ndescription: Configure a reverse proxy for Seerr.\nsidebar_position: 1\n---\n\n# Reverse Proxy\n\n:::"
  },
  {
    "path": "docs/getting-started/_category_.json",
    "chars": 50,
    "preview": "{\n  \"label\": \"Getting Started\",\n  \"position\": 2\n}\n"
  },
  {
    "path": "docs/getting-started/buildfromsource.mdx",
    "chars": 7542,
    "preview": "---\ntitle: Build From Source (Advanced)\ndescription: Install Seerr by building from source\nsidebar_position: 2\n---\n# Bui"
  },
  {
    "path": "docs/getting-started/docker.mdx",
    "chars": 9299,
    "preview": "---\ntitle: Docker (Recommended)\ndescription: Install Seerr using Docker\nsidebar_position: 1\n---\n# Docker\n:::info\nThis is"
  },
  {
    "path": "docs/getting-started/index.mdx",
    "chars": 242,
    "preview": "---\ntitle: Getting Started\n---\nimport DocCardList from '@theme/DocCardList';\n\n:::info\nAfter running Seerr for the first "
  },
  {
    "path": "docs/getting-started/kubernetes.mdx",
    "chars": 1233,
    "preview": "---\ntitle: Kubernetes (Advanced)\ndescription: Install Seerr in Kubernetes\nsidebar_position: 3\n---\n# Kubernetes\n:::warnin"
  },
  {
    "path": "docs/getting-started/third-parties/aur.mdx",
    "chars": 1058,
    "preview": "---\ntitle: AUR (Advanced)\ndescription: Install Seerr using the Arch User Repository\nsidebar_position: 2\n---\n\n# AUR\n:::wa"
  },
  {
    "path": "docs/getting-started/third-parties/index.mdx",
    "chars": 369,
    "preview": "---\ntitle: Third-party Installation Methods\n---\nimport DocCardList from '@theme/DocCardList';\n\n:::warning\nThird-party in"
  },
  {
    "path": "docs/getting-started/third-parties/nixpkg.mdx",
    "chars": 946,
    "preview": "---\ntitle: Nix Package Manager (Advanced)\ndescription: Install Seerr using Nixpkgs\nsidebar_position: 1\n---\n\nimport { See"
  },
  {
    "path": "docs/getting-started/third-parties/synology.mdx",
    "chars": 2958,
    "preview": "---\ntitle: Synology (Advanced)\ndescription: Install Seerr on Synology NAS using SynoCommunity\nsidebar_position: 5\n---\n\n#"
  },
  {
    "path": "docs/getting-started/third-parties/truenas.mdx",
    "chars": 524,
    "preview": "---\ntitle: TrueNAS (Advanced)\ndescription: Install Seerr using TrueNAS\nsidebar_position: 4\n---\n# TrueNAS\n:::warning\nThir"
  },
  {
    "path": "docs/getting-started/third-parties/unraid.mdx",
    "chars": 5684,
    "preview": "---\ntitle: Unraid (Advanced)\ndescription: Install Seerr using Unraid\nsidebar_position: 3\n---\n\nimport Tabs from '@theme/T"
  },
  {
    "path": "docs/migration-guide.mdx",
    "chars": 10452,
    "preview": "---\ntitle: Migration guide\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n:::important\nRead"
  },
  {
    "path": "docs/troubleshooting.mdx",
    "chars": 6387,
    "preview": "---\ntitle: Troubleshooting\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n## [TMDB] failed "
  },
  {
    "path": "docs/using-seerr/_category_.json",
    "chars": 166,
    "preview": "{\n  \"label\": \"Using Seerr\",\n  \"position\": 2,\n  \"link\": {\n    \"type\": \"generated-index\",\n    \"title\": \"Using Seerr\",\n    "
  },
  {
    "path": "docs/using-seerr/advanced/index.mdx",
    "chars": 312,
    "preview": "---\ntitle: Advanced Features\ndescription: Advanced configuration and use cases.\nsidebar_position: 6\n---\n\n# Advanced Feat"
  },
  {
    "path": "docs/using-seerr/advanced/verifying-signed-artifacts.mdx",
    "chars": 11794,
    "preview": "---\nid: verifying-signed-artifacts\ntitle: Verifying Signed Artifacts\nsidebar_label: Verify Signed Artifacts\ndescription:"
  },
  {
    "path": "docs/using-seerr/backups.md",
    "chars": 3061,
    "preview": "---\ntitle: Backups\ndescription: Understand which data you should back up.\nsidebar_position: 4\n---\n\n# Which data does See"
  },
  {
    "path": "docs/using-seerr/notifications/discord.md",
    "chars": 1094,
    "preview": "---\ntitle: Discord\ndescription: Configure Discord notifications.\nsidebar_position: 3\n---\n\n# Discord\n\nThe Discord notific"
  },
  {
    "path": "docs/using-seerr/notifications/email.md",
    "chars": 2093,
    "preview": "---\ntitle: Email\ndescription: Configure email notifications for your users.\nsidebar_position: 1\n---\n\n# Email\n\n## Configu"
  },
  {
    "path": "docs/using-seerr/notifications/gotify.md",
    "chars": 434,
    "preview": "---\ntitle: Gotify\ndescription: Configure Gotify notifications.\nsidebar_position: 5\n---\n\n# Gotify\n\n## Configuration\n\n### "
  },
  {
    "path": "docs/using-seerr/notifications/index.mdx",
    "chars": 819,
    "preview": "---\ntitle: Notifications\ndescription: Configure notifications for your users.\nsidebar_position: 3\n---\n\n# Notifications\n\n"
  },
  {
    "path": "docs/using-seerr/notifications/ntfy.md",
    "chars": 829,
    "preview": "---\ntitle: ntfy.sh\ndescription: Configure ntfy.sh notifications.\nsidebar_position: 6\n---\n\n# ntfy.sh\n\n## Configuration\n\n#"
  },
  {
    "path": "docs/using-seerr/notifications/pushbullet.md",
    "chars": 678,
    "preview": "---\ntitle: Pushbullet\ndescription: Configure Pushbullet notifications.\nsidebar_position: 7\n---\n\n# Pushbullet\n\n:::info\nUs"
  },
  {
    "path": "docs/using-seerr/notifications/pushover.md",
    "chars": 1036,
    "preview": "---\ntitle: Pushover\ndescription: Configure Pushover notifications.\nsidebar_position: 8\n---\n\n# Pushover\n\n:::info\nUsers ca"
  },
  {
    "path": "docs/using-seerr/notifications/slack.md",
    "chars": 396,
    "preview": "---\ntitle: Slack\ndescription: Configure Slack notifications.\nsidebar_position: 9\n---\n\n# Slack\n\n## Configuration\n\n### Web"
  },
  {
    "path": "docs/using-seerr/notifications/telegram.md",
    "chars": 1309,
    "preview": "---\ntitle: Telegram\ndescription: Configure Telegram notifications.\nsidebar_position: 10\n---\n\n# Telegram\n\n:::info\nUsers c"
  },
  {
    "path": "docs/using-seerr/notifications/webhook.md",
    "chars": 9426,
    "preview": "---\ntitle: Webhook\ndescription: Configure webhook notifications.\nsidebar_position: 4\n---\n\n# Webhook\n\nThe webhook notific"
  },
  {
    "path": "docs/using-seerr/notifications/webpush.md",
    "chars": 1268,
    "preview": "---\ntitle: Web Push\ndescription: Configure web push notifications for your users.\nsidebar_position: 2\n---\n\n# Web Push\n\n:"
  },
  {
    "path": "docs/using-seerr/plex/_category_.json",
    "chars": 199,
    "preview": "{\n  \"label\": \"Plex Integration\",\n  \"position\": 3,\n  \"link\": {\n    \"type\": \"generated-index\",\n    \"title\": \"Plex Integrat"
  },
  {
    "path": "docs/using-seerr/plex/index.md",
    "chars": 1185,
    "preview": "---\ntitle: Overview\ndescription: Learn about Seerr's Plex integration features\nsidebar_position: 1\n---\n\n# Plex Features "
  },
  {
    "path": "docs/using-seerr/plex/watchlist-auto-request.md",
    "chars": 3545,
    "preview": "---\ntitle: Watchlist Auto Request\ndescription: Learn how to use the Plex Watchlist Auto Request feature\nsidebar_position"
  },
  {
    "path": "docs/using-seerr/settings/_category_.json",
    "chars": 168,
    "preview": "{\n  \"label\": \"Settings\",\n  \"position\": 1,\n  \"link\": {\n    \"type\": \"generated-index\",\n    \"title\": \"Settings\",\n    \"descr"
  },
  {
    "path": "docs/using-seerr/settings/dns-caching.md",
    "chars": 999,
    "preview": "---\ntitle: DNS Caching\ndescription: Configure DNS caching settings.\nsidebar_position: 7\n---\n\n# DNS Caching\n\nSeerr uses D"
  },
  {
    "path": "docs/using-seerr/settings/general.md",
    "chars": 4980,
    "preview": "---\ntitle: General\ndescription: Configure global and default settings for Seerr.\nsidebar_position: 1\n---\n\n# General\n\n## "
  },
  {
    "path": "docs/using-seerr/settings/jobs&cache.md",
    "chars": 554,
    "preview": "---\ntitle: Jobs & Cache\ndescription: Configure jobs and cache settings.\nsidebar_position: 6\n---\n\n# Jobs & Cache\n\nSeerr p"
  },
  {
    "path": "docs/using-seerr/settings/mediaserver.mdx",
    "chars": 12769,
    "preview": "---\ntitle: Mediaserver Settings\ndescription: Configure your Jellyfin, Emby, or Plex server settings.\nsidebar_position: 3"
  },
  {
    "path": "docs/using-seerr/settings/notifications.mdx",
    "chars": 206,
    "preview": "---\ntitle: Notifications\ndescription: Configure notifications for your users.\nsidebar_position: 5\n---\n\n# Notifications\n\n"
  },
  {
    "path": "docs/using-seerr/settings/services.md",
    "chars": 3611,
    "preview": "---\ntitle: Services\ndescription: Configure your default services.\nsidebar_position: 4\n---\n\n# Services\n\n:::info\n**If you "
  },
  {
    "path": "docs/using-seerr/settings/users.md",
    "chars": 2079,
    "preview": "---\ntitle: User Settings\ndescription: Configure global and default user settings.\nsidebar_position: 2\n---\n\n# Users\n\n## E"
  },
  {
    "path": "docs/using-seerr/users/_category_.json",
    "chars": 158,
    "preview": "{\n  \"label\": \"Users\",\n  \"position\": 2,\n  \"link\": {\n    \"type\": \"generated-index\",\n    \"title\": \"Users\",\n    \"description"
  },
  {
    "path": "docs/using-seerr/users/adding-users.mdx",
    "chars": 3530,
    "preview": "---\ntitle: Adding Users\ndescription: Add users to your Seerr instance.\nsidebar_position: 2\n---\n\n# Adding Users\n\nThere ar"
  },
  {
    "path": "docs/using-seerr/users/deleting-users.md",
    "chars": 204,
    "preview": "---\ntitle: Deleting Users\ndescription: Delete users from Seerr.\nsidebar_position: 4\n---\n\n# Deleting Users\n\nWhen users ar"
  },
  {
    "path": "docs/using-seerr/users/editing-users.md",
    "chars": 2535,
    "preview": "---\ntitle: Editing Users\ndescription: Edit user settings and permissions.\nsidebar_position: 3\n---\n\n# Editing Users\n\nFrom"
  },
  {
    "path": "docs/using-seerr/users/owner.md",
    "chars": 688,
    "preview": "---\ntitle: Owner Account\ndescription: Your owner account is the primary account for managing Seerr.\nsidebar_position: 1\n"
  },
  {
    "path": "eslint.config.mts",
    "chars": 2568,
    "preview": "import js from '@eslint/js';\nimport nextPlugin from '@next/eslint-plugin-next';\nimport prettier from 'eslint-config-pret"
  },
  {
    "path": "gen-docs/.gitignore",
    "chars": 233,
    "preview": "# Dependencies\n/node_modules\n\n# Production\n/build\n\n# Generated files\n.docusaurus\n.cache-loader\n\n# Misc\n.DS_Store\n.env.lo"
  },
  {
    "path": "gen-docs/README.md",
    "chars": 593,
    "preview": "# Seerr Documentation\n\nSeerr docs is built using [Docusaurus](https://docusaurus.io/), a modern static website generator"
  },
  {
    "path": "gen-docs/babel.config.js",
    "chars": 89,
    "preview": "module.exports = {\n  presets: [require.resolve('@docusaurus/core/lib/babel/preset')],\n};\n"
  },
  {
    "path": "gen-docs/blog/2025-09-29-introducing-seerr-blog.md",
    "chars": 896,
    "preview": "---\ntitle: Welcome to the Seerr Blog\ndescription: The official Seerr blog for release notes, technical updates, and comm"
  },
  {
    "path": "gen-docs/blog/2026-02-10/seerr-release.md",
    "chars": 7963,
    "preview": "---\ntitle: \"Seerr Release: Unifying Overseerr and Jellyseerr\"\ndescription: \"Overseerr and Jellyseerr are merging into a "
  },
  {
    "path": "gen-docs/blog/2026-02-28-seerr-security-fix-release.md",
    "chars": 4239,
    "preview": "---\ntitle: \"Seerr v3.1.0: Critical Security Release\"\ndescription: \"Seerr v3.1.0 addresses three CVEs, including a high-p"
  },
  {
    "path": "gen-docs/blog/authors.yml",
    "chars": 893,
    "preview": "fallenbagel:\n  name: Fallenbagel\n  page: true\n  title: Developer & Maintainer of Seerr\n  description: Core Maintainer & "
  },
  {
    "path": "gen-docs/docusaurus.config.ts",
    "chars": 3214,
    "preview": "import type * as Preset from '@docusaurus/preset-classic';\nimport type { Config } from '@docusaurus/types';\nimport { the"
  },
  {
    "path": "gen-docs/package.json",
    "chars": 1267,
    "preview": "{\n  \"name\": \"gen-docs\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"packageManager\": \"pnpm@10.17.1\",\n  \"scripts\": {\n    "
  },
  {
    "path": "gen-docs/sidebars.ts",
    "chars": 787,
    "preview": "import type { SidebarsConfig } from '@docusaurus/plugin-content-docs';\n\n/**\n * Creating a sidebar enables you to:\n - cre"
  },
  {
    "path": "gen-docs/src/components/SeerrVersion/index.tsx",
    "chars": 2197,
    "preview": "import { useEffect, useState } from 'react';\n\nexport const SeerrVersion = () => {\n  const [version, setVersion] = useSta"
  },
  {
    "path": "gen-docs/src/css/custom.css",
    "chars": 3502,
    "preview": "/**\n * Any CSS included here will be global. The classic template\n * bundles Infima by default. Infima is a CSS framewor"
  },
  {
    "path": "gen-docs/static/.nojekyll",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "gen-docs/static/CNAME",
    "chars": 15,
    "preview": "docs.seerr.dev\n"
  },
  {
    "path": "gen-docs/tailwind.config.js",
    "chars": 116,
    "preview": "module.exports = {\n  content: ['./src/components/**/*.{ts,tsx}'],\n  theme: {\n    extend: {},\n  },\n  plugins: [],\n};\n"
  },
  {
    "path": "gen-docs/tsconfig.json",
    "chars": 176,
    "preview": "{\n  // This file is not used in compilation. It is here just for a nice editor experience.\n  \"extends\": \"@docusaurus/tsc"
  },
  {
    "path": "next-env.d.ts",
    "chars": 230,
    "preview": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n\n// NOTE: This file should not be edite"
  },
  {
    "path": "next.config.js",
    "chars": 580,
    "preview": "/**\n * @type {import('next').NextConfig}\n */\nmodule.exports = {\n  env: {\n    commitTag: process.env.COMMIT_TAG || 'local"
  },
  {
    "path": "package.json",
    "chars": 7525,
    "preview": "{\n  \"name\": \"seerr\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"packageManager\": \"pnpm@10.24.0\",\n  \"scripts\": {\n    \"pr"
  },
  {
    "path": "postcss.config.js",
    "chars": 83,
    "preview": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n};\n"
  },
  {
    "path": "public/offline.html",
    "chars": 1858,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"I"
  },
  {
    "path": "public/robots.txt",
    "chars": 26,
    "preview": "User-agent: *\nDisallow: /\n"
  },
  {
    "path": "public/site.webmanifest",
    "chars": 1620,
    "preview": "{\n  \"name\": \"Seerr\",\n  \"short_name\": \"Seerr\",\n  \"start_url\": \"./\",\n  \"icons\": [\n    {\n      \"src\": \"./android-chrome-192"
  },
  {
    "path": "public/sw.js",
    "chars": 4617,
    "preview": "/* eslint-disable no-undef */\n// Incrementing OFFLINE_VERSION will kick off the install event and force\n// previously ca"
  },
  {
    "path": "seerr-api.yml",
    "chars": 218039,
    "preview": "openapi: '3.0.2'\ninfo:\n  title: 'Seerr API'\n  version: '1.0.0'\n  description: |\n    This is the documentation for the Se"
  },
  {
    "path": "server/api/animelist.ts",
    "chars": 7364,
    "preview": "import logger from '@server/logger';\nimport axios from 'axios';\nimport fs, { promises as fsp } from 'fs';\nimport path fr"
  },
  {
    "path": "server/api/externalapi.ts",
    "chars": 3954,
    "preview": "import { requestInterceptorFunction } from '@server/utils/customProxyAgent';\nimport type { AxiosInstance, AxiosRequestCo"
  },
  {
    "path": "server/api/github.ts",
    "chars": 2705,
    "preview": "import cacheManager from '@server/lib/cache';\nimport logger from '@server/logger';\nimport ExternalAPI from './externalap"
  },
  {
    "path": "server/api/jellyfin.ts",
    "chars": 12991,
    "preview": "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport ExternalAPI from '@server/api/externalapi';\nimport { ApiE"
  },
  {
    "path": "server/api/metadata.ts",
    "chars": 1000,
    "preview": "import type { TvShowProvider } from '@server/api/provider';\nimport TheMovieDb from '@server/api/themoviedb';\nimport Tvdb"
  },
  {
    "path": "server/api/plexapi.ts",
    "chars": 5789,
    "preview": "import ExternalAPI from '@server/api/externalapi';\nimport type { Library, PlexSettings } from '@server/lib/settings';\nim"
  },
  {
    "path": "server/api/plextv.ts",
    "chars": 10724,
    "preview": "import type { PlexDevice } from '@server/interfaces/api/plexInterfaces';\nimport cacheManager from '@server/lib/cache';\ni"
  },
  {
    "path": "server/api/provider.ts",
    "chars": 551,
    "preview": "import type {\n  TmdbSeasonWithEpisodes,\n  TmdbTvDetails,\n} from '@server/api/themoviedb/interfaces';\n\nexport interface T"
  },
  {
    "path": "server/api/pushover.ts",
    "chars": 1140,
    "preview": "import ExternalAPI from './externalapi';\n\ninterface PushoverSoundsResponse {\n  sounds: {\n    [name: string]: string;\n  }"
  },
  {
    "path": "server/api/rating/imdbRadarrProxy.ts",
    "chars": 3707,
    "preview": "import ExternalAPI from '@server/api/externalapi';\nimport cacheManager from '@server/lib/cache';\n\ntype IMDBRadarrProxyRe"
  },
  {
    "path": "server/api/rating/rottentomatoes.ts",
    "chars": 6808,
    "preview": "import ExternalAPI from '@server/api/externalapi';\nimport cacheManager from '@server/lib/cache';\nimport { getSettings } "
  },
  {
    "path": "server/api/ratings.ts",
    "chars": 212,
    "preview": "import { type IMDBRating } from '@server/api/rating/imdbRadarrProxy';\nimport { type RTRating } from '@server/api/rating/"
  },
  {
    "path": "server/api/servarr/base.ts",
    "chars": 5455,
    "preview": "import ExternalAPI from '@server/api/externalapi';\nimport type { AvailableCacheIds } from '@server/lib/cache';\nimport ca"
  },
  {
    "path": "server/api/servarr/radarr.ts",
    "chars": 8227,
    "preview": "import logger from '@server/logger';\nimport ServarrBase from './base';\n\nexport interface RadarrMovieOptions {\n  title: s"
  },
  {
    "path": "server/api/servarr/sonarr.ts",
    "chars": 11970,
    "preview": "import logger from '@server/logger';\nimport ServarrBase from './base';\n\nexport interface SonarrSeason {\n  seasonNumber: "
  },
  {
    "path": "server/api/tautulli.ts",
    "chars": 7547,
    "preview": "import type { User } from '@server/entity/User';\nimport type { TautulliSettings } from '@server/lib/settings';\nimport lo"
  },
  {
    "path": "server/api/themoviedb/constants.ts",
    "chars": 40,
    "preview": "export const ANIME_KEYWORD_ID = 210024;\n"
  },
  {
    "path": "server/api/themoviedb/index.ts",
    "chars": 32630,
    "preview": "import ExternalAPI from '@server/api/externalapi';\nimport type { TvShowProvider } from '@server/api/provider';\nimport ca"
  },
  {
    "path": "server/api/themoviedb/interfaces.ts",
    "chars": 9485,
    "preview": "interface TmdbMediaResult {\n  id: number;\n  media_type: string;\n  popularity: number;\n  poster_path?: string;\n  backdrop"
  },
  {
    "path": "server/api/tvdb/index.ts",
    "chars": 13981,
    "preview": "import ExternalAPI from '@server/api/externalapi';\nimport type { TvShowProvider } from '@server/api/provider';\nimport Th"
  },
  {
    "path": "server/api/tvdb/interfaces.ts",
    "chars": 4708,
    "preview": "import { type AvailableLocale } from '@server/types/languages';\n\nexport interface TvdbBaseResponse<T> {\n  data: T;\n  err"
  },
  {
    "path": "server/constants/discover.ts",
    "chars": 1807,
    "preview": "import type DiscoverSlider from '@server/entity/DiscoverSlider';\n\nexport enum DiscoverSliderType {\n  RECENTLY_ADDED = 1,"
  },
  {
    "path": "server/constants/error.ts",
    "chars": 407,
    "preview": "export enum ApiErrorCode {\n  InvalidUrl = 'INVALID_URL',\n  InvalidCredentials = 'INVALID_CREDENTIALS',\n  InvalidAuthToke"
  },
  {
    "path": "server/constants/issue.ts",
    "chars": 301,
    "preview": "export enum IssueType {\n  VIDEO = 1,\n  AUDIO = 2,\n  SUBTITLES = 3,\n  OTHER = 4,\n}\n\nexport enum IssueStatus {\n  OPEN = 1,"
  },
  {
    "path": "server/constants/media.ts",
    "chars": 287,
    "preview": "export enum MediaRequestStatus {\n  PENDING = 1,\n  APPROVED,\n  DECLINED,\n  FAILED,\n  COMPLETED,\n}\n\nexport enum MediaType "
  },
  {
    "path": "server/constants/server.ts",
    "chars": 152,
    "preview": "export enum MediaServerType {\n  PLEX = 1,\n  JELLYFIN,\n  EMBY,\n  NOT_CONFIGURED,\n}\n\nexport enum ServerType {\n  JELLYFIN ="
  }
]

// ... and 587 more files (download for full content)

About this extraction

This page contains the full source code of the seerr-team/seerr GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 787 files (7.9 MB), approximately 2.1M tokens, and a symbol index with 1308 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!