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
10 changed files with 1368 additions and 205 deletions

View File

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

View File

@@ -50,7 +50,7 @@ const DiscoverTvNetwork = () => {
{firstResultData?.network.logoPath ? (
<div className="mb-6 flex justify-center">
<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}
className="max-h-24 sm:max-h-32"
fill

View File

@@ -50,7 +50,7 @@ const DiscoverMovieStudio = () => {
{firstResultData?.studio.logoPath ? (
<div className="mb-6 flex justify-center">
<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}
className="max-h-24 sm:max-h-32"
fill

View File

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

View File

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

View File

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

View File

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

View File

@@ -57,44 +57,48 @@ const ShowMoreCard = ({ url, posters }: ShowMoreCardProps) => {
>
<div style={{ paddingBottom: '150%' }}>
<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] && (
<div className="w-1/2 p-1">
<div className="">
<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=""
className="w-full rounded-md"
fill
className="rounded-md"
width={300}
height={450}
/>
</div>
)}
{posters[1] && (
<div className="w-1/2 p-1">
<div className="">
<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=""
className="w-full rounded-md"
fill
className="rounded-md"
width={300}
height={450}
/>
</div>
)}
{posters[2] && (
<div className="w-1/2 p-1">
<div className="">
<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=""
className="w-full rounded-md"
fill
className="rounded-md"
width={300}
height={450}
/>
</div>
)}
{posters[3] && (
<div className="w-1/2 p-1">
<div className="">
<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=""
className="w-full rounded-md"
fill
className="rounded-md"
width={300}
height={450}
/>
</div>
)}

View File

@@ -252,7 +252,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
const searchParams = new URLSearchParams(params.enable ? params : {});
const res = await fetch(
`/api/v1/settings/jellyfin/library?${searchParams.toString}`
`/api/v1/settings/jellyfin/library?${searchParams.toString()}`
);
if (!res.ok) throw new Error();
} else {

File diff suppressed because it is too large Load Diff