Compare commits

..

5 Commits

Author SHA1 Message Date
gauthier-th
b92fe7821e feat: test force Ipv4 for Axios 2025-06-11 11:15:58 +02:00
Ludovic Ortega
0bdc8a0334 chore(helm): bump jellyseerr to 2.6.0 (#1704)
Signed-off-by: Ludovic Ortega <ludovic.ortega@adminafk.fr>
2025-06-10 22:41:41 +02:00
Gauthier
c0dd2e5e27 fix(filters): display the right value when resetting the filter options (#1695)
* fix(filters): display the right value when resetting the filter options

When clearing the filters everything is reset but the sort option still display the previous value,
while the content has now the default value.

fix #1693

* fix: remove console.log
2025-06-04 17:15:09 +08:00
fallenbagel
6b8c0bd8f3 fix(mediarequests): properly sort season numbers in media requests (#1688)
Added an @AfterLoad() hook in MediaRequest to ensure seasons are always ordered by their ID.
Previously, joinedseasons could appear in arbitrary database order, leading to jumbled UI displays.
With this change,the seasons list is automatically re-sorted in ascending ID order as soon as
TypeORM hydrates theentity.

fix #1336
2025-05-31 11:23:15 +02:00
fallenbagel
ea7e68fc99 fix(usersettings): exclude current user when checking for existing email (#1689)
This update modifies the user settings endpoint so that, when validating email uniqueness, the query
excludes the current user’s own record (using id: Not(user.id))
2025-05-31 11:10:13 +02:00
35 changed files with 98 additions and 174 deletions

View File

@@ -3,8 +3,8 @@ kubeVersion: ">=1.23.0-0"
name: jellyseerr-chart
description: Jellyseerr helm chart for Kubernetes
type: application
version: 2.4.0
appVersion: "2.5.2"
version: 2.5.0
appVersion: "2.6.0"
maintainers:
- name: Jellyseerr
url: https://github.com/Fallenbagel/jellyseerr

View File

@@ -1,6 +1,6 @@
# jellyseerr-chart
![Version: 2.4.0](https://img.shields.io/badge/Version-2.4.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.5.2](https://img.shields.io/badge/AppVersion-2.5.2-informational?style=flat-square)
![Version: 2.5.0](https://img.shields.io/badge/Version-2.5.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.6.0](https://img.shields.io/badge/AppVersion-2.6.0-informational?style=flat-square)
Jellyseerr helm chart for Kubernetes

View File

@@ -1,15 +1,9 @@
/**
* @type {import('next').NextConfig}
*/
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path');
module.exports = {
basePath: process.env.NEXT_PUBLIC_BASE_PATH || '',
assetPrefix: process.env.NEXT_PUBLIC_BASE_PATH || '',
env: {
commitTag: process.env.COMMIT_TAG || 'local',
basePath: process.env.NEXT_PUBLIC_BASE_PATH || '',
},
images: {
remotePatterns: [
@@ -25,9 +19,6 @@ module.exports = {
issuer: /\.(js|ts)x?$/,
use: ['@svgr/webpack'],
});
config.resolve.alias['next/image'] = path.resolve(
'./src/components/Common/BaseImage/index.ts'
);
return config;
},

View File

@@ -17,6 +17,7 @@ import { DbAwareColumn } from '@server/utils/DbColumnHelper';
import { truncate } from 'lodash';
import {
AfterInsert,
AfterLoad,
AfterUpdate,
Column,
Entity,
@@ -701,6 +702,13 @@ export class MediaRequest {
}
}
@AfterLoad()
private sortSeasons() {
if (Array.isArray(this.seasons)) {
this.seasons.sort((a, b) => a.id - b.id);
}
}
static async sendNotification(
entity: MediaRequest,
media: Media,

View File

@@ -28,6 +28,7 @@ import { getAppVersion } from '@server/utils/appVersion';
import createCustomProxyAgent from '@server/utils/customProxyAgent';
import restartFlag from '@server/utils/restartFlag';
import { getClientIp } from '@supercharge/request-ip';
import axios from 'axios';
import { TypeormStore } from 'connect-typeorm/out';
import cookieParser from 'cookie-parser';
import type { NextFunction, Request, Response } from 'express';
@@ -35,11 +36,16 @@ import express from 'express';
import * as OpenApiValidator from 'express-openapi-validator';
import type { Store } from 'express-session';
import session from 'express-session';
import http from 'http';
import https from 'https';
import next from 'next';
import path from 'path';
import swaggerUi from 'swagger-ui-express';
import YAML from 'yamljs';
axios.defaults.httpAgent = new http.Agent({ family: 4 });
axios.defaults.httpsAgent = new https.Agent({ family: 4 });
const API_SPEC_PATH = path.join(__dirname, '../jellyseerr-api.yml');
logger.info(`Starting Jellyseerr version ${getAppVersion()}`);
@@ -154,13 +160,11 @@ app
});
if (settings.network.csrfProtection) {
server.use(
`${process.env.NEXT_PUBLIC_BASE_PATH || ''}`,
csurf({
cookie: {
httpOnly: true,
sameSite: true,
secure: !dev,
path: `${process.env.NEXT_PUBLIC_BASE_PATH || ''}` || '/',
},
})
);
@@ -176,7 +180,7 @@ app
// Set up sessions
const sessionRespository = getRepository(Session);
server.use(
`${process.env.NEXT_PUBLIC_BASE_PATH || ''}/api`,
'/api',
session({
secret: settings.clientId,
resave: false,
@@ -186,7 +190,6 @@ app
httpOnly: true,
sameSite: settings.network.csrfProtection ? 'strict' : 'lax',
secure: 'auto',
path: `${process.env.NEXT_PUBLIC_BASE_PATH || ''}` || '/',
},
store: new TypeormStore({
cleanupLimit: 2,
@@ -195,13 +198,8 @@ app
})
);
const apiDocs = YAML.load(API_SPEC_PATH);
server.use('/api-docs', swaggerUi.serve, swaggerUi.setup(apiDocs));
server.use(
`${process.env.NEXT_PUBLIC_BASE_PATH || ''}/api-docs`,
swaggerUi.serve,
swaggerUi.setup(apiDocs)
);
server.use(
`${process.env.NEXT_PUBLIC_BASE_PATH || ''}`,
OpenApiValidator.middleware({
apiSpec: API_SPEC_PATH,
validateRequests: true,
@@ -219,12 +217,11 @@ app
};
next();
});
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
server.use(`${basePath}/api/v1`, routes);
server.use('/api/v1', routes);
// Do not set cookies so CDNs can cache them
server.use(`${basePath}/imageproxy`, clearCookies, imageproxy);
server.use(`${basePath}/avatarproxy`, clearCookies, avatarproxy);
server.use('/imageproxy', clearCookies, imageproxy);
server.use('/avatarproxy', clearCookies, avatarproxy);
server.get('*', (req, res) => handle(req, res));
server.use(

View File

@@ -18,6 +18,7 @@ import { ApiError } from '@server/types/error';
import { getHostname } from '@server/utils/getHostname';
import { Router } from 'express';
import net from 'net';
import { Not } from 'typeorm';
import { canMakePermissionsChange } from '.';
const isOwnProfile = (): Middleware => {
@@ -125,8 +126,9 @@ userSettingsRoutes.post<
}
const existingUser = await userRepository.findOne({
where: { email: user.email },
where: { email: user.email, id: Not(user.id) },
});
if (oldEmail !== user.email && existingUser) {
throw new ApiError(400, ApiErrorCode.InvalidEmail);
}

View File

@@ -12,7 +12,6 @@ import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import Error from '@app/pages/_error';
import defineMessages from '@app/utils/defineMessages';
import { getBasedPath } from '@app/utils/navigationUtil';
import {
ChevronLeftIcon,
ChevronRightIcon,
@@ -123,7 +122,7 @@ const Blacklist = () => {
onChange={(e) => {
setCurrentFilter(e.target.value as Filter);
router.push({
pathname: getBasedPath(router.pathname),
pathname: router.pathname,
query: router.query.userId
? { userId: router.query.userId }
: {},

View File

@@ -1,32 +0,0 @@
// src/components/Common/BaseImage/index.ts
import type { ImageProps } from 'next/image';
import NextImage from 'next/image';
import React from 'react';
// Instead of defining our own props, extend from Next's ImageProps
const BaseImage = React.forwardRef<HTMLImageElement, ImageProps>(
(props, ref) => {
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
const modifiedSrc =
typeof props.src === 'string' && props.src.startsWith('/')
? `${basePath}${props.src}`
: props.src;
const shouldUnoptimize =
typeof props.src === 'string' && props.src.endsWith('.svg');
return React.createElement(NextImage, {
...props,
ref,
src: modifiedSrc,
unoptimized: shouldUnoptimize || props.unoptimized,
});
}
);
BaseImage.displayName = 'Image';
export default BaseImage;
// Re-export ImageProps type for consumers
export type { ImageProps };

View File

@@ -1,6 +1,6 @@
import Image from '@app/components/Common/BaseImage';
import useSettings from '@app/hooks/useSettings';
import type { ImageLoader, ImageProps } from 'next/image';
import Image from 'next/image';
const imageLoader: ImageLoader = ({ src }) => src;

View File

@@ -1,5 +1,4 @@
import { useUser } from '@app/hooks/useUser';
import { getBasedPath } from '@app/utils/navigationUtil';
import type { Permission } from '@server/lib/permissions';
import { hasPermission } from '@server/lib/permissions';
import Link from 'next/link';
@@ -86,10 +85,10 @@ const SettingsTabs = ({
</label>
<select
onChange={(e) => {
router.push(getBasedPath(e.target.value));
router.push(e.target.value);
}}
onBlur={(e) => {
router.push(getBasedPath(e.target.value));
router.push(e.target.value);
}}
defaultValue={
settingsRoutes.find((route) => !!router.pathname.match(route.regex))

View File

@@ -85,7 +85,7 @@ const DiscoverMovies = () => {
id="sortBy"
name="sortBy"
className="rounded-r-only"
value={preparedFilters.sortBy}
value={preparedFilters.sortBy || SortOptions.PopularityDesc}
onChange={(e) => updateQueryParams('sortBy', e.target.value)}
>
<option value={SortOptions.PopularityDesc}>

View File

@@ -83,7 +83,7 @@ const DiscoverTv = () => {
id="sortBy"
name="sortBy"
className="rounded-r-only"
value={preparedFilters.sortBy}
value={preparedFilters.sortBy || SortOptions.PopularityDesc}
onChange={(e) => updateQueryParams('sortBy', e.target.value)}
>
<option value={SortOptions.PopularityDesc}>

View File

@@ -13,7 +13,6 @@ import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import ErrorPage from '@app/pages/_error';
import defineMessages from '@app/utils/defineMessages';
import { getBasedPath } from '@app/utils/navigationUtil';
import { Transition } from '@headlessui/react';
import {
ChatBubbleOvalLeftEllipsisIcon,
@@ -167,7 +166,7 @@ const IssueDetails = () => {
appearance: 'success',
autoDismiss: true,
});
router.push(getBasedPath('/issues'));
router.push('/issues');
} catch (e) {
addToast(intl.formatMessage(messages.toastissuedeletefailed), {
appearance: 'error',

View File

@@ -6,7 +6,6 @@ import IssueItem from '@app/components/IssueList/IssueItem';
import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { getBasedPath } from '@app/utils/navigationUtil';
import {
BarsArrowDownIcon,
ChevronLeftIcon,
@@ -108,7 +107,7 @@ const IssueList = () => {
onChange={(e) => {
setCurrentFilter(e.target.value as Filter);
router.push({
pathname: getBasedPath(router.pathname),
pathname: router.pathname,
query: router.query.userId
? { userId: router.query.userId }
: {},
@@ -138,7 +137,7 @@ const IssueList = () => {
onChange={(e) => {
setCurrentSort(e.target.value as Sort);
router.push({
pathname: getBasedPath(router.pathname),
pathname: router.pathname,
query: router.query.userId
? { userId: router.query.userId }
: {},

View File

@@ -1,5 +1,4 @@
import Badge from '@app/components/Common/Badge';
import Image from '@app/components/Common/BaseImage';
import UserWarnings from '@app/components/Layout/UserWarnings';
import VersionStatus from '@app/components/Layout/VersionStatus';
import useClickOutside from '@app/hooks/useClickOutside';
@@ -17,6 +16,7 @@ import {
UsersIcon,
XMarkIcon,
} from '@heroicons/react/24/outline';
import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { Fragment, useEffect, useRef } from 'react';

View File

@@ -1,7 +1,6 @@
import EmbyLogo from '@app/assets/services/emby-icon-only.svg';
import JellyfinLogo from '@app/assets/services/jellyfin-icon.svg';
import PlexLogo from '@app/assets/services/plex.svg';
import Image from '@app/components/Common/BaseImage';
import Button from '@app/components/Common/Button';
import ImageFader from '@app/components/Common/ImageFader';
import PageTitle from '@app/components/Common/PageTitle';
@@ -12,12 +11,12 @@ import PlexLoginButton from '@app/components/Login/PlexLoginButton';
import useSettings from '@app/hooks/useSettings';
import { useUser } from '@app/hooks/useUser';
import defineMessages from '@app/utils/defineMessages';
import { getBasedPath } from '@app/utils/navigationUtil';
import { Transition } from '@headlessui/react';
import { XCircleIcon } from '@heroicons/react/24/solid';
import { MediaServerType } from '@server/constants/server';
import axios from 'axios';
import { useRouter } from 'next/dist/client/router';
import Image from 'next/image';
import { useEffect, useRef, useState } from 'react';
import { useIntl } from 'react-intl';
import { CSSTransition, SwitchTransition } from 'react-transition-group';
@@ -45,8 +44,6 @@ const Login = () => {
settings.currentSettings.mediaServerLogin
);
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
// Effect that is triggered when the `authToken` comes back from the Plex OAuth
// We take the token and attempt to sign in. If we get a success message, we will
// ask swr to revalidate the user which _should_ come back with a valid user.
@@ -74,7 +71,7 @@ const Login = () => {
// valid user, we redirect the user to the home page as the login was successful.
useEffect(() => {
if (user) {
router.push(getBasedPath('/'));
router.push('/');
}
}, [user, router]);
@@ -132,7 +129,7 @@ const Login = () => {
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={basePath + '/os_icon.svg'}
src="/os_icon.svg"
alt={settings.currentSettings.applicationTitle}
className="mr-2 h-5"
/>

View File

@@ -30,7 +30,6 @@ import globalMessages from '@app/i18n/globalMessages';
import ErrorPage from '@app/pages/_error';
import { sortCrewPriority } from '@app/utils/creditHelpers';
import defineMessages from '@app/utils/defineMessages';
import { getBasedPath } from '@app/utils/navigationUtil';
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
import {
ArrowRightCircleIcon,
@@ -469,7 +468,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
onClose={() => {
setShowManager(false);
router.push({
pathname: getBasedPath(router.pathname),
pathname: router.pathname,
query: { movieId: router.query.movieId },
});
}}

View File

@@ -3,154 +3,153 @@ interface PWAHeaderProps {
}
const PWAHeader = ({ applicationTitle = 'Jellyseerr' }: PWAHeaderProps) => {
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
return (
<>
<link
rel="apple-touch-icon"
sizes="180x180"
href={`${basePath}/apple-touch-icon.png`}
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href={`${basePath}/favicon-32x32.png`}
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href={`${basePath}/favicon-16x16.png`}
href="/favicon-16x16.png"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-2048-2732.jpg`}
href="/apple-splash-2048-2732.jpg"
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-2732-2048.jpg`}
href="/apple-splash-2732-2048.jpg"
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-1668-2388.jpg`}
href="/apple-splash-1668-2388.jpg"
media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-2388-1668.jpg`}
href="/apple-splash-2388-1668.jpg"
media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-1536-2048.jpg`}
href="/apple-splash-1536-2048.jpg"
media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-2048-1536.jpg`}
href="/apple-splash-2048-1536.jpg"
media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-1668-2224.jpg`}
href="/apple-splash-1668-2224.jpg"
media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-2224-1668.jpg`}
href="/apple-splash-2224-1668.jpg"
media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-1620-2160.jpg`}
href="/apple-splash-1620-2160.jpg"
media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-2160-1620.jpg`}
href="/apple-splash-2160-1620.jpg"
media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-1284-2778.jpg`}
href="/apple-splash-1284-2778.jpg"
media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-2778-1284.jpg`}
href="/apple-splash-2778-1284.jpg"
media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-1170-2532.jpg`}
href="/apple-splash-1170-2532.jpg"
media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-2532-1170.jpg`}
href="/apple-splash-2532-1170.jpg"
media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-1125-2436.jpg`}
href="/apple-splash-1125-2436.jpg"
media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-2436-1125.jpg`}
href="/apple-splash-2436-1125.jpg"
media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-1242-2688.jpg`}
href="/apple-splash-1242-2688.jpg"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-2688-1242.jpg`}
href="/apple-splash-2688-1242.jpg"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-828-1792.jpg`}
href="/apple-splash-828-1792.jpg"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-1792-828.jpg`}
href="/apple-splash-1792-828.jpg"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-1242-2208.jpg`}
href="/apple-splash-1242-2208.jpg"
media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-2208-1242.jpg`}
href="/apple-splash-2208-1242.jpg"
media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-750-1334.jpg`}
href="/apple-splash-750-1334.jpg"
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-1334-750.jpg`}
href="/apple-splash-1334-750.jpg"
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-640-1136.jpg`}
href="/apple-splash-640-1136.jpg"
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href={`${basePath}/apple-splash-1136-640.jpg`}
href="/apple-splash-1136-640.jpg"
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<meta name="apple-mobile-web-app-capable" content="yes" />
@@ -160,7 +159,7 @@ const PWAHeader = ({ applicationTitle = 'Jellyseerr' }: PWAHeaderProps) => {
/>
<link
rel="manifest"
href={`${basePath}/site.webmanifest`}
href="/site.webmanifest"
crossOrigin="use-credentials"
/>
<meta name="apple-mobile-web-app-title" content={applicationTitle} />

View File

@@ -8,7 +8,6 @@ import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams';
import { useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { getBasedPath } from '@app/utils/navigationUtil';
import {
ArrowDownIcon,
ArrowUpIcon,
@@ -174,7 +173,7 @@ const RequestList = () => {
onChange={(e) => {
setCurrentMediaType(e.target.value as MediaType);
router.push({
pathname: getBasedPath(router.pathname),
pathname: router.pathname,
query: router.query.userId
? { userId: router.query.userId }
: {},
@@ -204,7 +203,7 @@ const RequestList = () => {
onChange={(e) => {
setCurrentFilter(e.target.value as Filter);
router.push({
pathname: getBasedPath(router.pathname),
pathname: router.pathname,
query: router.query.userId
? { userId: router.query.userId }
: {},
@@ -252,7 +251,7 @@ const RequestList = () => {
onChange={(e) => {
setCurrentSort(e.target.value as Sort);
router.push({
pathname: getBasedPath(router.pathname),
pathname: router.pathname,
query: router.query.userId
? { userId: router.query.userId }
: {},

View File

@@ -1,4 +1,3 @@
import Image from '@app/components/Common/BaseImage';
import Button from '@app/components/Common/Button';
import ImageFader from '@app/components/Common/ImageFader';
import PageTitle from '@app/components/Common/PageTitle';
@@ -7,6 +6,7 @@ import defineMessages from '@app/utils/defineMessages';
import { ArrowLeftIcon, EnvelopeIcon } from '@heroicons/react/24/solid';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import Image from 'next/image';
import Link from 'next/link';
import { useState } from 'react';
import { useIntl } from 'react-intl';

View File

@@ -1,4 +1,3 @@
import Image from '@app/components/Common/BaseImage';
import Button from '@app/components/Common/Button';
import ImageFader from '@app/components/Common/ImageFader';
import SensitiveInput from '@app/components/Common/SensitiveInput';
@@ -8,6 +7,7 @@ import defineMessages from '@app/utils/defineMessages';
import { LifebuoyIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Form, Formik } from 'formik';
import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useState } from 'react';

View File

@@ -10,7 +10,6 @@ import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams';
import globalMessages from '@app/i18n/globalMessages';
import Error from '@app/pages/_error';
import defineMessages from '@app/utils/defineMessages';
import { getBasedPath } from '@app/utils/navigationUtil';
import { Transition } from '@headlessui/react';
import {
ChevronLeftIcon,
@@ -287,7 +286,7 @@ const SettingsLogs = () => {
name="filter"
onChange={(e) => {
setCurrentFilter(e.target.value as Filter);
router.push(getBasedPath(router.pathname));
router.push(router.pathname);
}}
value={currentFilter}
className="rounded-r-only"

View File

@@ -2,7 +2,6 @@ import EmbyLogo from '@app/assets/services/emby.svg';
import JellyfinLogo from '@app/assets/services/jellyfin.svg';
import PlexLogo from '@app/assets/services/plex.svg';
import AppDataWarning from '@app/components/AppDataWarning';
import Image from '@app/components/Common/BaseImage';
import Button from '@app/components/Common/Button';
import ImageFader from '@app/components/Common/ImageFader';
import PageTitle from '@app/components/Common/PageTitle';
@@ -14,10 +13,10 @@ import SetupSteps from '@app/components/Setup/SetupSteps';
import useLocale from '@app/hooks/useLocale';
import useSettings from '@app/hooks/useSettings';
import defineMessages from '@app/utils/defineMessages';
import { getBasedPath } from '@app/utils/navigationUtil';
import { MediaServerType } from '@server/constants/server';
import type { Library } from '@server/lib/settings';
import axios from 'axios';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { useCallback, useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -68,7 +67,7 @@ const Setup = () => {
await axios.post('/api/v1/settings/main', { locale });
mutate('/api/v1/settings/public');
router.push(getBasedPath('/'));
router.push('/');
}
};
@@ -109,7 +108,7 @@ const Setup = () => {
useEffect(() => {
if (settings.currentSettings.initialized) {
router.push(getBasedPath('/'));
router.push('/');
}
if (

View File

@@ -33,7 +33,6 @@ import globalMessages from '@app/i18n/globalMessages';
import Error from '@app/pages/_error';
import { sortCrewPriority } from '@app/utils/creditHelpers';
import defineMessages from '@app/utils/defineMessages';
import { getBasedPath } from '@app/utils/navigationUtil';
import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper';
import { Disclosure, Transition } from '@headlessui/react';
import {
@@ -521,7 +520,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
onClose={() => {
setShowManager(false);
router.push({
pathname: getBasedPath(router.pathname),
pathname: router.pathname,
query: { tvId: router.query.tvId },
});
}}

View File

@@ -1,10 +1,10 @@
import Alert from '@app/components/Common/Alert';
import Image from '@app/components/Common/BaseImage';
import Modal from '@app/components/Common/Modal';
import useSettings from '@app/hooks/useSettings';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import axios from 'axios';
import Image from 'next/image';
import { useState } from 'react';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';

View File

@@ -16,7 +16,6 @@ import type { User } from '@app/hooks/useUser';
import { Permission, UserType, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { getBasedPath } from '@app/utils/navigationUtil';
import { Transition } from '@headlessui/react';
import {
BarsArrowDownIcon,
@@ -551,7 +550,7 @@ const UserList = () => {
name="sort"
onChange={(e) => {
setCurrentSort(e.target.value as Sort);
router.push(getBasedPath(router.pathname));
router.push(router.pathname);
}}
value={currentSort}
className="rounded-r-only"
@@ -718,7 +717,7 @@ const UserList = () => {
className="mr-2"
onClick={() =>
router.push(
getBasedPath('/users/[userId]/settings'),
'/users/[userId]/settings',
`/users/${user.id}/settings`
)
}

View File

@@ -17,7 +17,6 @@ export const UserContext = ({ initialUser, children }: UserContextProps) => {
const { user, error, revalidate } = useUser({ initialData: initialUser });
const router = useRouter();
const routing = useRef(false);
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
useEffect(() => {
revalidate();
@@ -30,9 +29,9 @@ export const UserContext = ({ initialUser, children }: UserContextProps) => {
!routing.current
) {
routing.current = true;
location.href = `${API_BASE}/login`;
location.href = '/login';
}
}, [router, user, error, API_BASE]);
}, [router, user, error]);
return <>{children}</>;
};

View File

@@ -78,8 +78,7 @@ const useDiscover = <
)
.join('&');
const fullEndpoint = endpoint.startsWith('/') ? `${endpoint}` : endpoint;
return `${fullEndpoint}?${finalQueryString}`;
return `${endpoint}?${finalQueryString}`;
},
{
initialSize: 3,

View File

@@ -1,4 +1,3 @@
import { getBasedPath } from '@app/utils/navigationUtil';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import type { Permission, PermissionCheckOptions } from './useUser';
@@ -13,7 +12,7 @@ const useRouteGuard = (
useEffect(() => {
if (user && !hasPermission(permission, options)) {
router.push(getBasedPath('/'));
router.push('/');
}
}, [user, permission, router, hasPermission, options]);
};

View File

@@ -1,4 +1,3 @@
import { getBasedPath } from '@app/utils/navigationUtil';
import type { NextRouter } from 'next/router';
import { useRouter } from 'next/router';
import type { ParsedUrlQuery } from 'querystring';
@@ -107,15 +106,9 @@ export const useQueryParams = (): UseQueryParamReturnedFunction => {
if (newRoute.path !== router.asPath) {
if (routerAction === 'replace') {
router.replace(
getBasedPath(newRoute.pathname),
getBasedPath(newRoute.path)
);
router.replace(newRoute.pathname, newRoute.path);
} else {
router.push(
getBasedPath(newRoute.pathname),
getBasedPath(newRoute.path)
);
router.push(newRoute.pathname, newRoute.path);
}
}
},

View File

@@ -99,14 +99,6 @@ const loadLocaleData = (locale: AvailableLocale): Promise<any> => {
}
};
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
axios.interceptors.request.use((config) => {
if (config.url?.startsWith('/')) {
config.url = `${API_BASE}${config.url}`;
}
return config;
});
// Custom types so we can correctly type our GetInitialProps function
// with our combined user prop
// This is specific to _app.tsx. Other pages will not need to do this!
@@ -193,9 +185,9 @@ const CoreApp: Omit<NextAppComponentType, 'origGetInitialProps'> = ({
return (
<SWRConfig
value={{
fetcher: async (url) => axios.get(url).then((res) => res.data),
fetcher: (url) => axios.get(url).then((res) => res.data),
fallback: {
[`${API_BASE}/api/v1/auth/me`]: user,
'/api/v1/auth/me': user,
},
}}
>
@@ -264,7 +256,7 @@ CoreApp.getInitialProps = async (initialProps) => {
const response = await axios.get<PublicSettingsResponse>(
`http://${process.env.HOST || 'localhost'}:${
process.env.PORT || 5055
}${API_BASE}/api/v1/settings/public`
}/api/v1/settings/public`
);
currentSettings = response.data;
@@ -274,7 +266,7 @@ CoreApp.getInitialProps = async (initialProps) => {
if (!initialized) {
if (!router.pathname.match(/(setup|login\/plex)/)) {
ctx.res.writeHead(307, {
Location: `${API_BASE}/setup`,
Location: '/setup',
});
ctx.res.end();
}
@@ -284,7 +276,7 @@ CoreApp.getInitialProps = async (initialProps) => {
const response = await axios.get<User>(
`http://${process.env.HOST || 'localhost'}:${
process.env.PORT || 5055
}${API_BASE}/api/v1/auth/me`,
}/api/v1/auth/me`,
{
headers:
ctx.req && ctx.req.headers.cookie
@@ -296,7 +288,7 @@ CoreApp.getInitialProps = async (initialProps) => {
if (router.pathname.match(/(setup|login)/)) {
ctx.res.writeHead(307, {
Location: `/`,
Location: '/',
});
ctx.res.end();
}
@@ -306,7 +298,7 @@ CoreApp.getInitialProps = async (initialProps) => {
// before anything actually renders
if (!router.pathname.match(/(login|setup|resetpassword)/)) {
ctx.res.writeHead(307, {
Location: `${API_BASE}/login`,
Location: '/login',
});
ctx.res.end();
}

View File

@@ -11,15 +11,13 @@ const CollectionPage: NextPage<CollectionPageProps> = ({ collection }) => {
return <CollectionDetails collection={collection} />;
};
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
export const getServerSideProps: GetServerSideProps<
CollectionPageProps
> = async (ctx) => {
const response = await axios.get<Collection>(
`http://${process.env.HOST || 'localhost'}:${
process.env.PORT || 5055
}${API_BASE}/api/v1/collection/${ctx.query.collectionId}`,
}/api/v1/collection/${ctx.query.collectionId}`,
{
headers: ctx.req?.headers?.cookie
? { cookie: ctx.req.headers.cookie }

View File

@@ -11,15 +11,13 @@ const MoviePage: NextPage<MoviePageProps> = ({ movie }) => {
return <MovieDetails movie={movie} />;
};
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
export const getServerSideProps: GetServerSideProps<MoviePageProps> = async (
ctx
) => {
const response = await axios.get<MovieDetailsType>(
`http://${process.env.HOST || 'localhost'}:${
process.env.PORT || 5055
}${API_BASE}/api/v1/movie/${ctx.query.movieId}`,
}/api/v1/movie/${ctx.query.movieId}`,
{
headers: ctx.req?.headers?.cookie
? { cookie: ctx.req.headers.cookie }

View File

@@ -11,15 +11,13 @@ const TvPage: NextPage<TvPageProps> = ({ tv }) => {
return <TvDetails tv={tv} />;
};
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
export const getServerSideProps: GetServerSideProps<TvPageProps> = async (
ctx
) => {
const response = await axios.get<TvDetailsType>(
`http://${process.env.HOST || 'localhost'}:${
process.env.PORT || 5055
}${API_BASE}/api/v1/tv/${ctx.query.tvId}`,
}/api/v1/tv/${ctx.query.tvId}`,
{
headers: ctx.req?.headers?.cookie
? { cookie: ctx.req.headers.cookie }

View File

@@ -1,4 +0,0 @@
export const getBasedPath = (path: string) => {
const API_BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
return path.startsWith('/') && path !== '/' ? `${API_BASE}${path}` : path;
};