Make admin movies/series search-first instead of loading the full library.
Avoids heavy mount queries so lookup stays fast and server load stays low.
This commit is contained in:
parent
14556c82f3
commit
23c81f2b83
3 changed files with 159 additions and 29 deletions
|
|
@ -81,6 +81,9 @@ export default function MoviesPage() {
|
||||||
const [nodeId, setNodeId] = useState("");
|
const [nodeId, setNodeId] = useState("");
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [pathSearch, setPathSearch] = useState("");
|
const [pathSearch, setPathSearch] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [searched, setSearched] = useState(false);
|
||||||
|
const [limitHit, setLimitHit] = useState(false);
|
||||||
const [expanded, setExpanded] = useState<string | null>(null);
|
const [expanded, setExpanded] = useState<string | null>(null);
|
||||||
const [msg, setMsg] = useState("");
|
const [msg, setMsg] = useState("");
|
||||||
const [busyFileId, setBusyFileId] = useState<string | null>(null);
|
const [busyFileId, setBusyFileId] = useState<string | null>(null);
|
||||||
|
|
@ -100,15 +103,31 @@ export default function MoviesPage() {
|
||||||
const [folderBusy, setFolderBusy] = useState(false);
|
const [folderBusy, setFolderBusy] = useState(false);
|
||||||
const [folderMsg, setFolderMsg] = useState("");
|
const [folderMsg, setFolderMsg] = useState("");
|
||||||
|
|
||||||
|
function hasSearchQuery(q = search, pathQ = pathSearch) {
|
||||||
|
return q.trim().length >= 2 || pathQ.trim().length >= 2;
|
||||||
|
}
|
||||||
|
|
||||||
function load(q = search, filterNodeId = nodeId, pathQ = pathSearch) {
|
function load(q = search, filterNodeId = nodeId, pathQ = pathSearch) {
|
||||||
|
if (!hasSearchQuery(q, pathQ)) {
|
||||||
|
setMovies([]);
|
||||||
|
setSearched(false);
|
||||||
|
setLimitHit(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (q.trim()) params.set("search", q.trim());
|
if (q.trim()) params.set("search", q.trim());
|
||||||
if (filterNodeId) params.set("nodeId", filterNodeId);
|
if (filterNodeId) params.set("nodeId", filterNodeId);
|
||||||
if (pathQ.trim()) params.set("path", pathQ.trim());
|
if (pathQ.trim()) params.set("path", pathQ.trim());
|
||||||
const qs = params.toString() ? `?${params}` : "";
|
setLoading(true);
|
||||||
fetch(`/api/v1/admin/movies${qs}`, { credentials: "include" })
|
setSearched(true);
|
||||||
|
fetch(`/api/v1/admin/movies?${params}`, { credentials: "include" })
|
||||||
.then((r) => r.json())
|
.then((r) => r.json())
|
||||||
.then((d) => setMovies(d.movies ?? []));
|
.then((d) => {
|
||||||
|
const list = d.movies ?? [];
|
||||||
|
setMovies(list);
|
||||||
|
setLimitHit(Boolean(d.limit && list.length >= d.limit));
|
||||||
|
})
|
||||||
|
.finally(() => setLoading(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -118,10 +137,10 @@ export default function MoviesPage() {
|
||||||
const list = (d.nodes ?? []) as NodeOption[];
|
const list = (d.nodes ?? []) as NodeOption[];
|
||||||
setNodes(list.filter((n) => n.id).sort((a, b) => a.name.localeCompare(b.name)));
|
setNodes(list.filter((n) => n.id).sort((a, b) => a.name.localeCompare(b.name)));
|
||||||
});
|
});
|
||||||
load();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!searched) return;
|
||||||
load(search, nodeId, pathSearch);
|
load(search, nodeId, pathSearch);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [nodeId]);
|
}, [nodeId]);
|
||||||
|
|
@ -384,6 +403,11 @@ export default function MoviesPage() {
|
||||||
<form
|
<form
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (!hasSearchQuery()) {
|
||||||
|
setMsg("Typ minstens 2 tekens in titel of pad.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setMsg("");
|
||||||
load();
|
load();
|
||||||
}}
|
}}
|
||||||
style={{ display: "flex", gap: "0.5rem", flexWrap: "wrap", alignItems: "center" }}
|
style={{ display: "flex", gap: "0.5rem", flexWrap: "wrap", alignItems: "center" }}
|
||||||
|
|
@ -404,8 +428,9 @@ export default function MoviesPage() {
|
||||||
<input
|
<input
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
placeholder="Zoek titel..."
|
placeholder="Zoek titel / IMDb…"
|
||||||
style={{ width: 180, marginBottom: 0 }}
|
style={{ width: 180, marginBottom: 0 }}
|
||||||
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
value={pathSearch}
|
value={pathSearch}
|
||||||
|
|
@ -413,12 +438,15 @@ export default function MoviesPage() {
|
||||||
placeholder="Zoek map/pad…"
|
placeholder="Zoek map/pad…"
|
||||||
style={{ width: 220, marginBottom: 0 }}
|
style={{ width: 220, marginBottom: 0 }}
|
||||||
/>
|
/>
|
||||||
<button type="submit">Zoek</button>
|
<button type="submit" disabled={loading}>
|
||||||
|
{loading ? "Zoeken…" : "Zoek"}
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="muted page-lead">
|
<p className="muted page-lead">
|
||||||
Serie per ongeluk als film? Open de titel → bij de map{" "}
|
Zoek op titel of pad (min. 2 tekens) — de hele bibliotheek wordt niet meer geladen. Serie per
|
||||||
|
ongeluk als film? Open de titel → bij de map{" "}
|
||||||
<strong style={{ color: "#a1a1aa", fontWeight: 600 }}>Als serie herkoppelen</strong>. Vereist
|
<strong style={{ color: "#a1a1aa", fontWeight: 600 }}>Als serie herkoppelen</strong>. Vereist
|
||||||
node 1.3.7+ met paden (anders eerst upgraden + rescan).
|
node 1.3.7+ met paden (anders eerst upgraden + rescan).
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -604,9 +632,18 @@ export default function MoviesPage() {
|
||||||
|
|
||||||
{movies.length === 0 && (
|
{movies.length === 0 && (
|
||||||
<div className="card" style={{ textAlign: "center", color: "#71717a" }}>
|
<div className="card" style={{ textAlign: "center", color: "#71717a" }}>
|
||||||
Geen movies gevonden
|
{loading
|
||||||
|
? "Zoeken…"
|
||||||
|
: searched
|
||||||
|
? "Geen movies gevonden"
|
||||||
|
: "Typ een titel of pad en klik Zoek"}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{limitHit && (
|
||||||
|
<p className="muted" style={{ marginTop: "0.75rem", textAlign: "center" }}>
|
||||||
|
Max. 50 resultaten — verfijn je zoekopdracht als je titel er niet tussen staat.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{linkMovieId && (
|
{linkMovieId && (
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,9 @@ export default function SeriesPage() {
|
||||||
const [nodeId, setNodeId] = useState("");
|
const [nodeId, setNodeId] = useState("");
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [pathSearch, setPathSearch] = useState("");
|
const [pathSearch, setPathSearch] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [searched, setSearched] = useState(false);
|
||||||
|
const [limitHit, setLimitHit] = useState(false);
|
||||||
const [expanded, setExpanded] = useState<string | null>(null);
|
const [expanded, setExpanded] = useState<string | null>(null);
|
||||||
const [busyFileId, setBusyFileId] = useState<string | null>(null);
|
const [busyFileId, setBusyFileId] = useState<string | null>(null);
|
||||||
const [msg, setMsg] = useState("");
|
const [msg, setMsg] = useState("");
|
||||||
|
|
@ -106,15 +109,31 @@ export default function SeriesPage() {
|
||||||
const [folderBusy, setFolderBusy] = useState(false);
|
const [folderBusy, setFolderBusy] = useState(false);
|
||||||
const [folderMsg, setFolderMsg] = useState("");
|
const [folderMsg, setFolderMsg] = useState("");
|
||||||
|
|
||||||
|
function hasSearchQuery(q = search, pathQ = pathSearch) {
|
||||||
|
return q.trim().length >= 2 || pathQ.trim().length >= 2;
|
||||||
|
}
|
||||||
|
|
||||||
function loadSeries(q = search, filterNodeId = nodeId, pathQ = pathSearch) {
|
function loadSeries(q = search, filterNodeId = nodeId, pathQ = pathSearch) {
|
||||||
|
if (!hasSearchQuery(q, pathQ)) {
|
||||||
|
setSeries([]);
|
||||||
|
setSearched(false);
|
||||||
|
setLimitHit(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (q.trim()) params.set("search", q.trim());
|
if (q.trim()) params.set("search", q.trim());
|
||||||
if (filterNodeId) params.set("nodeId", filterNodeId);
|
if (filterNodeId) params.set("nodeId", filterNodeId);
|
||||||
if (pathQ.trim()) params.set("path", pathQ.trim());
|
if (pathQ.trim()) params.set("path", pathQ.trim());
|
||||||
const qs = params.toString() ? `?${params}` : "";
|
setLoading(true);
|
||||||
fetch(`/api/v1/admin/series${qs}`, { credentials: "include" })
|
setSearched(true);
|
||||||
|
fetch(`/api/v1/admin/series?${params}`, { credentials: "include" })
|
||||||
.then((r) => r.json())
|
.then((r) => r.json())
|
||||||
.then((d) => setSeries(d.series ?? []));
|
.then((d) => {
|
||||||
|
const list = d.series ?? [];
|
||||||
|
setSeries(list);
|
||||||
|
setLimitHit(Boolean(d.limit && list.length >= d.limit));
|
||||||
|
})
|
||||||
|
.finally(() => setLoading(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPathFiles(pathQ = pathSearch, filterNodeId = nodeId) {
|
function loadPathFiles(pathQ = pathSearch, filterNodeId = nodeId) {
|
||||||
|
|
@ -122,6 +141,10 @@ export default function SeriesPage() {
|
||||||
setPathFiles(null);
|
setPathFiles(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!pathQ.trim()) {
|
||||||
|
setPathFiles(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (pathQ.trim()) params.set("path", pathQ.trim());
|
if (pathQ.trim()) params.set("path", pathQ.trim());
|
||||||
if (filterNodeId) params.set("nodeId", filterNodeId);
|
if (filterNodeId) params.set("nodeId", filterNodeId);
|
||||||
|
|
@ -143,10 +166,10 @@ export default function SeriesPage() {
|
||||||
const list = (d.nodes ?? []) as NodeOption[];
|
const list = (d.nodes ?? []) as NodeOption[];
|
||||||
setNodes(list.filter((n) => n.id).sort((a, b) => a.name.localeCompare(b.name)));
|
setNodes(list.filter((n) => n.id).sort((a, b) => a.name.localeCompare(b.name)));
|
||||||
});
|
});
|
||||||
load();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!searched) return;
|
||||||
load(search, nodeId, pathSearch);
|
load(search, nodeId, pathSearch);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [nodeId]);
|
}, [nodeId]);
|
||||||
|
|
@ -401,6 +424,11 @@ export default function SeriesPage() {
|
||||||
<form
|
<form
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (!hasSearchQuery()) {
|
||||||
|
setMsg("Typ minstens 2 tekens in titel of pad.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setMsg("");
|
||||||
load();
|
load();
|
||||||
}}
|
}}
|
||||||
style={{ display: "flex", gap: "0.5rem", flexWrap: "wrap", alignItems: "center" }}
|
style={{ display: "flex", gap: "0.5rem", flexWrap: "wrap", alignItems: "center" }}
|
||||||
|
|
@ -422,8 +450,9 @@ export default function SeriesPage() {
|
||||||
<input
|
<input
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
placeholder="Zoek titel..."
|
placeholder="Zoek titel / IMDb…"
|
||||||
style={{ width: 180, marginBottom: 0 }}
|
style={{ width: 180, marginBottom: 0 }}
|
||||||
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
value={pathSearch}
|
value={pathSearch}
|
||||||
|
|
@ -431,17 +460,21 @@ export default function SeriesPage() {
|
||||||
placeholder="Zoek map/pad… (bv. Breaking Bad)"
|
placeholder="Zoek map/pad… (bv. Breaking Bad)"
|
||||||
style={{ width: 260, marginBottom: 0 }}
|
style={{ width: 260, marginBottom: 0 }}
|
||||||
/>
|
/>
|
||||||
<button type="submit">Zoek</button>
|
<button type="submit" disabled={loading}>
|
||||||
|
{loading ? "Zoeken…" : "Zoek"}
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="muted page-lead">
|
<p className="muted page-lead">
|
||||||
Per node zie je de show-map. Padzoeken toont ook losse bestanden om foutmatches
|
Zoek op titel of pad (min. 2 tekens) — de hele bibliotheek wordt niet meer geladen. Per node
|
||||||
opnieuw te koppelen.{" "}
|
zie je de show-map. Padzoeken toont ook losse bestanden om foutmatches opnieuw te koppelen.
|
||||||
{nodeId
|
{searched
|
||||||
|
? nodeId
|
||||||
? ` Filter: ${selectedNodeName ?? "node"} · ${series.length} series.`
|
? ` Filter: ${selectedNodeName ?? "node"} · ${series.length} series.`
|
||||||
: `${series.length} series.`}
|
: ` ${series.length} series.`
|
||||||
{pathSearch.trim() ? ` Padfilter: “${pathSearch.trim()}”.` : ""}
|
: ""}
|
||||||
|
{pathSearch.trim() && searched ? ` Padfilter: “${pathSearch.trim()}”.` : ""}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{msg && (
|
{msg && (
|
||||||
|
|
@ -660,11 +693,20 @@ export default function SeriesPage() {
|
||||||
|
|
||||||
{series.length === 0 && (
|
{series.length === 0 && (
|
||||||
<div className="card" style={{ textAlign: "center", color: "#71717a" }}>
|
<div className="card" style={{ textAlign: "center", color: "#71717a" }}>
|
||||||
{nodeId || pathSearch
|
{loading
|
||||||
|
? "Zoeken…"
|
||||||
|
: searched
|
||||||
|
? nodeId || pathSearch
|
||||||
? "Geen series voor dit filter. Check scan-roots / pad / rescan."
|
? "Geen series voor dit filter. Check scan-roots / pad / rescan."
|
||||||
: "Geen series gevonden"}
|
: "Geen series gevonden"
|
||||||
|
: "Typ een titel of pad en klik Zoek"}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{limitHit && (
|
||||||
|
<p className="muted" style={{ marginTop: "0.75rem", textAlign: "center" }}>
|
||||||
|
Max. 50 resultaten — verfijn je zoekopdracht als je titel er niet tussen staat.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{linkSeriesId && (
|
{linkSeriesId && (
|
||||||
|
|
|
||||||
|
|
@ -136,15 +136,38 @@ export async function registerAdminRoutes(app: FastifyInstance, config: Config)
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get("/api/v1/admin/movies", { preHandler: requireAdmin }, async (request) => {
|
app.get("/api/v1/admin/movies", { preHandler: requireAdmin }, async (request) => {
|
||||||
const query = request.query as { search?: string; nodeId?: string; path?: string };
|
const query = request.query as {
|
||||||
|
search?: string;
|
||||||
|
nodeId?: string;
|
||||||
|
path?: string;
|
||||||
|
limit?: string;
|
||||||
|
};
|
||||||
const nodeId = query.nodeId?.trim() || undefined;
|
const nodeId = query.nodeId?.trim() || undefined;
|
||||||
const pathQ = query.path?.trim() || undefined;
|
const pathQ = query.path?.trim() || undefined;
|
||||||
const searchQ = query.search?.trim() || undefined;
|
const searchQ = query.search?.trim() || undefined;
|
||||||
|
const hasQuery =
|
||||||
|
(searchQ != null && searchQ.length >= 2) || (pathQ != null && pathQ.length >= 2);
|
||||||
|
if (!hasQuery) {
|
||||||
|
return { movies: [], total: 0, requiresQuery: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
const limitRaw = Number.parseInt(query.limit ?? "50", 10);
|
||||||
|
const take = Number.isFinite(limitRaw)
|
||||||
|
? Math.min(Math.max(limitRaw, 1), 100)
|
||||||
|
: 50;
|
||||||
|
|
||||||
const movies = await prisma.movie.findMany({
|
const movies = await prisma.movie.findMany({
|
||||||
where: {
|
where: {
|
||||||
AND: [
|
AND: [
|
||||||
searchQ ? { title: { contains: searchQ, mode: "insensitive" } } : {},
|
searchQ
|
||||||
|
? {
|
||||||
|
OR: [
|
||||||
|
{ title: { contains: searchQ, mode: "insensitive" } },
|
||||||
|
{ originalTitle: { contains: searchQ, mode: "insensitive" } },
|
||||||
|
{ imdbId: { contains: searchQ, mode: "insensitive" } },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: {},
|
||||||
nodeId || pathQ
|
nodeId || pathQ
|
||||||
? {
|
? {
|
||||||
mediaFiles: {
|
mediaFiles: {
|
||||||
|
|
@ -163,7 +186,7 @@ export async function registerAdminRoutes(app: FastifyInstance, config: Config)
|
||||||
include: { node: { select: { id: true, name: true, status: true } } },
|
include: { node: { select: { id: true, name: true, status: true } } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
take: 5000,
|
take,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -228,6 +251,9 @@ export async function registerAdminRoutes(app: FastifyInstance, config: Config)
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
total: movies.length,
|
||||||
|
limit: take,
|
||||||
|
requiresQuery: false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -244,15 +270,37 @@ export async function registerAdminRoutes(app: FastifyInstance, config: Config)
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get("/api/v1/admin/series", { preHandler: requireAdmin }, async (request) => {
|
app.get("/api/v1/admin/series", { preHandler: requireAdmin }, async (request) => {
|
||||||
const query = request.query as { search?: string; nodeId?: string; path?: string };
|
const query = request.query as {
|
||||||
|
search?: string;
|
||||||
|
nodeId?: string;
|
||||||
|
path?: string;
|
||||||
|
limit?: string;
|
||||||
|
};
|
||||||
const nodeId = query.nodeId?.trim() || undefined;
|
const nodeId = query.nodeId?.trim() || undefined;
|
||||||
const pathQ = query.path?.trim() || undefined;
|
const pathQ = query.path?.trim() || undefined;
|
||||||
const searchQ = query.search?.trim() || undefined;
|
const searchQ = query.search?.trim() || undefined;
|
||||||
|
const hasQuery =
|
||||||
|
(searchQ != null && searchQ.length >= 2) || (pathQ != null && pathQ.length >= 2);
|
||||||
|
if (!hasQuery) {
|
||||||
|
return { series: [], total: 0, requiresQuery: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
const limitRaw = Number.parseInt(query.limit ?? "50", 10);
|
||||||
|
const take = Number.isFinite(limitRaw)
|
||||||
|
? Math.min(Math.max(limitRaw, 1), 100)
|
||||||
|
: 50;
|
||||||
|
|
||||||
const series = await prisma.series.findMany({
|
const series = await prisma.series.findMany({
|
||||||
where: {
|
where: {
|
||||||
AND: [
|
AND: [
|
||||||
searchQ ? { title: { contains: searchQ, mode: "insensitive" } } : {},
|
searchQ
|
||||||
|
? {
|
||||||
|
OR: [
|
||||||
|
{ title: { contains: searchQ, mode: "insensitive" } },
|
||||||
|
{ imdbId: { contains: searchQ, mode: "insensitive" } },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: {},
|
||||||
nodeId || pathQ
|
nodeId || pathQ
|
||||||
? {
|
? {
|
||||||
seasons: {
|
seasons: {
|
||||||
|
|
@ -293,7 +341,7 @@ export async function registerAdminRoutes(app: FastifyInstance, config: Config)
|
||||||
orderBy: { seasonNumber: "asc" },
|
orderBy: { seasonNumber: "asc" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
take: 2000,
|
take,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -397,6 +445,9 @@ export async function registerAdminRoutes(app: FastifyInstance, config: Config)
|
||||||
})).filter((season) => (!nodeId && !pathQ) || season.episodes.length > 0),
|
})).filter((season) => (!nodeId && !pathQ) || season.episodes.length > 0),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
total: series.length,
|
||||||
|
limit: take,
|
||||||
|
requiresQuery: false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue