Compare commits

...

6 Commits

Author SHA1 Message Date
gauthier-th
2786424f70 fix: add some missing getBasedPath 2025-05-27 20:50:14 +02:00
gauthier-th
89c4e119fe fix: remove some redundant basePath calls 2025-05-27 17:24:43 +02:00
gauthier-th
9d9ce8072a style: run prettier 2025-05-26 18:00:08 +02:00
gauthier-th
65d9efbd8e refactor: update to Axios 2025-05-26 17:55:13 +02:00
Aidan Hilt
2f14de8545 fix: experimental basePath support PR bugs (#1409)
* Bugfixes for the experimental basepath feature

* Added a redirect to baseURL for the user context

* Addressing PR comments

* Reverting formatting changes to _app.tsx

* Running pnpm format
2025-05-26 17:44:32 +02:00
fallenbagel
1d5378cf35 feat: experimental basePath support when built-from-source 2025-05-26 17:43:46 +02:00
29 changed files with 168 additions and 76 deletions

View File

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

View File

@@ -154,11 +154,13 @@ app
}); });
if (settings.network.csrfProtection) { if (settings.network.csrfProtection) {
server.use( server.use(
`${process.env.NEXT_PUBLIC_BASE_PATH || ''}`,
csurf({ csurf({
cookie: { cookie: {
httpOnly: true, httpOnly: true,
sameSite: true, sameSite: true,
secure: !dev, secure: !dev,
path: `${process.env.NEXT_PUBLIC_BASE_PATH || ''}` || '/',
}, },
}) })
); );
@@ -174,7 +176,7 @@ app
// Set up sessions // Set up sessions
const sessionRespository = getRepository(Session); const sessionRespository = getRepository(Session);
server.use( server.use(
'/api', `${process.env.NEXT_PUBLIC_BASE_PATH || ''}/api`,
session({ session({
secret: settings.clientId, secret: settings.clientId,
resave: false, resave: false,
@@ -184,6 +186,7 @@ app
httpOnly: true, httpOnly: true,
sameSite: settings.network.csrfProtection ? 'strict' : 'lax', sameSite: settings.network.csrfProtection ? 'strict' : 'lax',
secure: 'auto', secure: 'auto',
path: `${process.env.NEXT_PUBLIC_BASE_PATH || ''}` || '/',
}, },
store: new TypeormStore({ store: new TypeormStore({
cleanupLimit: 2, cleanupLimit: 2,
@@ -192,8 +195,13 @@ app
}) })
); );
const apiDocs = YAML.load(API_SPEC_PATH); const apiDocs = YAML.load(API_SPEC_PATH);
server.use('/api-docs', swaggerUi.serve, swaggerUi.setup(apiDocs));
server.use( 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({ OpenApiValidator.middleware({
apiSpec: API_SPEC_PATH, apiSpec: API_SPEC_PATH,
validateRequests: true, validateRequests: true,
@@ -211,11 +219,12 @@ app
}; };
next(); next();
}); });
server.use('/api/v1', routes); const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
server.use(`${basePath}/api/v1`, routes);
// Do not set cookies so CDNs can cache them // Do not set cookies so CDNs can cache them
server.use('/imageproxy', clearCookies, imageproxy); server.use(`${basePath}/imageproxy`, clearCookies, imageproxy);
server.use('/avatarproxy', clearCookies, avatarproxy); server.use(`${basePath}/avatarproxy`, clearCookies, avatarproxy);
server.get('*', (req, res) => handle(req, res)); server.get('*', (req, res) => handle(req, res));
server.use( server.use(

View File

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

View File

@@ -0,0 +1,32 @@
// 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 useSettings from '@app/hooks/useSettings';
import type { ImageLoader, ImageProps } from 'next/image'; import type { ImageLoader, ImageProps } from 'next/image';
import Image from 'next/image';
const imageLoader: ImageLoader = ({ src }) => src; const imageLoader: ImageLoader = ({ src }) => src;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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