Compare commits

..

7 Commits

Author SHA1 Message Date
Gauthier
f2a4b70ea0 fix: rewrite the rate limit utility 2024-07-26 15:07:24 +02:00
Fallenbagel
62dbde448c revert: fix(api): fix nextjs error handler (#882) (#892)
This commit reverts the nextjs error handler fix that was introduced in #882 as that change requires
further refactor which should be held off for another version owing to the fact that there are
currently a lot of changes ready for the next version of jellyseerr.
2024-07-25 16:48:29 +05:00
Gauthier
0116c13e06 fix(api): fix nextjs error handler (#882)
This PR removes a custom error handler that sometimes caused issues by sending headers after some
content had already been sent.
2024-07-24 21:31:18 +02:00
Nir Israel Hen
c96ca6742e feat(translation): added full Hebrew translation (#871)
* feat(translation): added full Hebrew translation

* Update he.json

fixed missing translations
2024-07-24 23:51:55 +05:00
Gauthier
c80d9a853a fix: remove protocol-relative URLs from next/image (#889)
Next.js image component doesn't support protocol-relative URLs, so this PR replaces them to https
URLs
2024-07-24 20:10:31 +02:00
Gauthier
6cea8bba59 fix: add missing brackets (#888) 2024-07-24 22:14:04 +05:00
Fallenbagel
2be9c7dcc1 fix: add missing content-type header (#887)
* fix: add missing headers when commenting on an issue

* fix: more missing content-type headers in post requests
2024-07-24 19:34:54 +05:00
9 changed files with 1367 additions and 204 deletions

View File

@@ -7,9 +7,10 @@ type RateLimiteState<T extends (...args: Parameters<T>) => Promise<U>, U> = {
queue: { queue: {
args: Parameters<T>; args: Parameters<T>;
resolve: (value: U) => void; resolve: (value: U) => void;
reject: (reason?: unknown) => void;
}[]; }[];
activeRequests: number; lastTimestamps: number[];
timer: NodeJS.Timeout | null; timeout: ReturnType<typeof setTimeout>;
}; };
const rateLimitById: Record<string, unknown> = {}; const rateLimitById: Record<string, unknown> = {};
@@ -27,46 +28,40 @@ export default function rateLimit<
>(fn: T, options: RateLimitOptions): (...args: Parameters<T>) => Promise<U> { >(fn: T, options: RateLimitOptions): (...args: Parameters<T>) => Promise<U> {
const state: RateLimiteState<T, U> = (rateLimitById[ const state: RateLimiteState<T, U> = (rateLimitById[
options.id || '' options.id || ''
] as RateLimiteState<T, U>) || { queue: [], activeRequests: 0, timer: null }; ] as RateLimiteState<T, U>) || { queue: [], lastTimestamps: [] };
if (options.id) { if (options.id) {
rateLimitById[options.id] = state; rateLimitById[options.id] = state;
} }
const processQueue = () => { const processQueue = () => {
if (state.queue.length === 0) { // remove old timestamps
if (state.timer) { state.lastTimestamps = state.lastTimestamps.filter(
clearInterval(state.timer); (timestamp) => Date.now() - timestamp < 1000
state.timer = null; );
}
return;
}
while (state.activeRequests < options.maxRPS) { if (state.lastTimestamps.length < options.maxRPS) {
state.activeRequests++; // process requests if RPS not exceeded
const item = state.queue.shift(); const item = state.queue.shift();
if (!item) break; if (!item) return;
const { args, resolve } = item; state.lastTimestamps.push(Date.now());
const { args, resolve, reject } = item;
fn(...args) fn(...args)
.then(resolve) .then(resolve)
.finally(() => { .catch(reject);
state.activeRequests--; processQueue();
if (state.queue.length > 0) { } else {
if (!state.timer) { // rerun once the oldest item in queue is older than 1s
state.timer = setInterval(processQueue, 1000); if (state.timeout) clearTimeout(state.timeout);
} state.timeout = setTimeout(
} else { processQueue,
if (state.timer) { 1000 - (Date.now() - state.lastTimestamps[0])
clearInterval(state.timer); );
state.timer = null;
}
}
});
} }
}; };
return (...args: Parameters<T>): Promise<U> => { return (...args: Parameters<T>): Promise<U> => {
return new Promise<U>((resolve) => { return new Promise<U>((resolve, reject) => {
state.queue.push({ args, resolve }); state.queue.push({ args, resolve, reject });
processQueue(); processQueue();
}); });
}; };

View File

@@ -50,7 +50,7 @@ const DiscoverTvNetwork = () => {
{firstResultData?.network.logoPath ? ( {firstResultData?.network.logoPath ? (
<div className="mb-6 flex justify-center"> <div className="mb-6 flex justify-center">
<Image <Image
src={`//image.tmdb.org/t/p/w780_filter(duotone,ffffff,bababa)${firstResultData.network.logoPath}`} src={`https://image.tmdb.org/t/p/w780_filter(duotone,ffffff,bababa)${firstResultData.network.logoPath}`}
alt={firstResultData.network.name} alt={firstResultData.network.name}
className="max-h-24 sm:max-h-32" className="max-h-24 sm:max-h-32"
fill fill

View File

@@ -50,7 +50,7 @@ const DiscoverMovieStudio = () => {
{firstResultData?.studio.logoPath ? ( {firstResultData?.studio.logoPath ? (
<div className="mb-6 flex justify-center"> <div className="mb-6 flex justify-center">
<Image <Image
src={`//image.tmdb.org/t/p/w780_filter(duotone,ffffff,bababa)${firstResultData.studio.logoPath}`} src={`https://image.tmdb.org/t/p/w780_filter(duotone,ffffff,bababa)${firstResultData.studio.logoPath}`}
alt={firstResultData.studio.name} alt={firstResultData.studio.name}
className="max-h-24 sm:max-h-32" className="max-h-24 sm:max-h-32"
fill fill

View File

@@ -181,6 +181,9 @@ const IssueComment = ({
`/api/v1/issueComment/${comment.id}`, `/api/v1/issueComment/${comment.id}`,
{ {
method: 'PUT', method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: values.newMessage }), body: JSON.stringify({ message: values.newMessage }),
} }
); );

View File

@@ -126,6 +126,9 @@ const IssueDetails = () => {
try { try {
const res = await fetch(`/api/v1/issueComment/${firstComment.id}`, { const res = await fetch(`/api/v1/issueComment/${firstComment.id}`, {
method: 'PUT', method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: newMessage }), body: JSON.stringify({ message: newMessage }),
}); });
if (!res.ok) throw new Error(); if (!res.ok) throw new Error();
@@ -501,6 +504,9 @@ const IssueDetails = () => {
`/api/v1/issue/${issueData?.id}/comment`, `/api/v1/issue/${issueData?.id}/comment`,
{ {
method: 'POST', method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: values.message }), body: JSON.stringify({ message: values.message }),
} }
); );

View File

@@ -59,6 +59,9 @@ const AddEmailModal: React.FC<AddEmailModalProps> = ({
try { try {
const res = await fetch('/api/v1/auth/jellyfin', { const res = await fetch('/api/v1/auth/jellyfin', {
method: 'POST', method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ body: JSON.stringify({
username: username, username: username,
password: password, password: password,

View File

@@ -45,6 +45,9 @@ const Login = () => {
try { try {
const res = await fetch('/api/v1/auth/plex', { const res = await fetch('/api/v1/auth/plex', {
method: 'POST', method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ authToken }), body: JSON.stringify({ authToken }),
}); });
if (!res.ok) throw new Error(); if (!res.ok) throw new Error();

View File

@@ -57,44 +57,48 @@ const ShowMoreCard = ({ url, posters }: ShowMoreCardProps) => {
> >
<div style={{ paddingBottom: '150%' }}> <div style={{ paddingBottom: '150%' }}>
<div className="absolute inset-0 flex h-full w-full flex-col items-center p-2"> <div className="absolute inset-0 flex h-full w-full flex-col items-center p-2">
<div className="relative z-10 flex h-full flex-wrap items-center justify-center opacity-30"> <div className="relative z-10 grid h-full w-full grid-cols-2 items-center justify-center gap-2 opacity-30">
{posters[0] && ( {posters[0] && (
<div className="w-1/2 p-1"> <div className="">
<Image <Image
src={`//image.tmdb.org/t/p/w300_and_h450_face${posters[0]}`} src={`https://image.tmdb.org/t/p/w300_and_h450_face${posters[0]}`}
alt="" alt=""
className="w-full rounded-md" className="rounded-md"
fill width={300}
height={450}
/> />
</div> </div>
)} )}
{posters[1] && ( {posters[1] && (
<div className="w-1/2 p-1"> <div className="">
<Image <Image
src={`//image.tmdb.org/t/p/w300_and_h450_face${posters[1]}`} src={`https://image.tmdb.org/t/p/w300_and_h450_face${posters[1]}`}
alt="" alt=""
className="w-full rounded-md" className="rounded-md"
fill width={300}
height={450}
/> />
</div> </div>
)} )}
{posters[2] && ( {posters[2] && (
<div className="w-1/2 p-1"> <div className="">
<Image <Image
src={`//image.tmdb.org/t/p/w300_and_h450_face${posters[2]}`} src={`https://image.tmdb.org/t/p/w300_and_h450_face${posters[2]}`}
alt="" alt=""
className="w-full rounded-md" className="rounded-md"
fill width={300}
height={450}
/> />
</div> </div>
)} )}
{posters[3] && ( {posters[3] && (
<div className="w-1/2 p-1"> <div className="">
<Image <Image
src={`//image.tmdb.org/t/p/w300_and_h450_face${posters[3]}`} src={`https://image.tmdb.org/t/p/w300_and_h450_face${posters[3]}`}
alt="" alt=""
className="w-full rounded-md" className="rounded-md"
fill width={300}
height={450}
/> />
</div> </div>
)} )}

File diff suppressed because it is too large Load Diff