diff --git a/packages/@liexp/shared/src/endpoints/admin.endpoints.ts b/packages/@liexp/shared/src/endpoints/admin.endpoints.ts
index 69f4869fd..5d9784f6c 100644
--- a/packages/@liexp/shared/src/endpoints/admin.endpoints.ts
+++ b/packages/@liexp/shared/src/endpoints/admin.endpoints.ts
@@ -98,12 +98,15 @@ export const GetMediaStats = Endpoint({
data: t.strict({
orphans: t.array(t.any),
match: t.array(t.any),
- temp: t.array(t.any)
+ temp: t.array(t.any),
+ noThumbnails: t.array(t.any)
}),
totals: t.strict({
orphans: t.number,
match: t.number,
- temp: t.number
+ temp: t.number,
+ noThumbnails: t.number
+
}),
total: t.number,
}),
diff --git a/packages/@liexp/shared/src/io/http/Media.ts b/packages/@liexp/shared/src/io/http/Media.ts
index 22d6e74d6..3ce0a7fbb 100644
--- a/packages/@liexp/shared/src/io/http/Media.ts
+++ b/packages/@liexp/shared/src/io/http/Media.ts
@@ -66,6 +66,7 @@ export const GetListMediaQuery = t.type(
ids: optionFromNullable(t.array(UUID)),
exclude: optionFromNullable(t.array(UUID)),
description: optionFromNullable(t.string),
+ emptyThumbnail: optionFromNullable(BooleanFromString),
emptyEvents: optionFromNullable(BooleanFromString),
emptyLinks: optionFromNullable(BooleanFromString),
emptyAreas: optionFromNullable(BooleanFromString),
diff --git a/packages/@liexp/ui/src/components/admin/media/MediaList.tsx b/packages/@liexp/ui/src/components/admin/media/MediaList.tsx
index dcf887e56..5286bf618 100644
--- a/packages/@liexp/ui/src/components/admin/media/MediaList.tsx
+++ b/packages/@liexp/ui/src/components/admin/media/MediaList.tsx
@@ -8,12 +8,12 @@ import {
FunctionField,
List,
LoadingPage,
+ NumberInput,
ReferenceField,
TextInput,
useGetIdentity,
usePermissions,
type ListProps,
- NumberInput,
} from "react-admin";
import { Box, Typography, amber } from "../../mui";
import { toFormattedDuration } from "./DurationField";
@@ -24,6 +24,7 @@ const RESOURCE = "media";
const mediaFilters = [
,
+ ,
,
,
,
diff --git a/services/admin-web/src/pages/dashboard/AdminStats.tsx b/services/admin-web/src/pages/dashboard/AdminStats.tsx
index f34ea43aa..54a68071e 100644
--- a/services/admin-web/src/pages/dashboard/AdminStats.tsx
+++ b/services/admin-web/src/pages/dashboard/AdminStats.tsx
@@ -11,8 +11,13 @@ import {
} from "@liexp/ui/lib/components/mui";
import { pipe } from "fp-ts/lib/function";
import * as React from "react";
+import { useNavigate } from "react-router";
export const AdminStats: React.FC = () => {
+ const navigate = useNavigate();
+ const onNoThumbnailsClick = React.useCallback(() => {
+ navigate(`/media?filter=${JSON.stringify({ emptyThumbnail: true })}`);
+ }, []);
return (
@@ -34,6 +39,18 @@ export const AdminStats: React.FC = () => {
Media
+
+ No Thumbnails:
+
+ {data.noThumbnails.length}
+
+
+
=
+ (ctx) => () => {
+ return pipe(
+ ctx.db.find(MediaEntity, {
+ where: {
+ thumbnail: IsNull(),
+ },
+ }),
+ );
+ };
+
+export const getMediaAdminStatsFlow: TEFlow<[], any> = (ctx) => () => {
+ return pipe(
+ sequenceS(fp.TE.ApplicativePar)({
+ orphans: getOrphanMediaFlow(ctx)(),
+ temp: getTempMediaCountFlow(ctx)(),
+ noThumbnails: getMediaWithoutThumbnailsFlow(ctx)(),
+ }),
+ );
+};
diff --git a/services/api/src/queries/media/fetchManyMedia.query.ts b/services/api/src/queries/media/fetchManyMedia.query.ts
index cd401760a..f1fc259e2 100644
--- a/services/api/src/queries/media/fetchManyMedia.query.ts
+++ b/services/api/src/queries/media/fetchManyMedia.query.ts
@@ -14,6 +14,7 @@ const defaultQuery: http.Media.GetListMediaQuery = {
description: fp.O.none,
events: fp.O.none,
keywords: fp.O.none,
+ emptyThumbnail: fp.O.none,
emptyEvents: fp.O.none,
emptyLinks: fp.O.none,
emptyAreas: fp.O.none,
@@ -39,6 +40,7 @@ export const fetchManyMedia: TEFlow<
events,
emptyEvents,
emptyLinks,
+ emptyThumbnail,
emptyAreas,
deletedOnly,
exclude,
@@ -130,6 +132,12 @@ export const fetchManyMedia: TEFlow<
}
}
+ if (fp.O.isSome(emptyThumbnail) && emptyThumbnail.value) {
+ const where = hasWhere ? q.andWhere.bind(q) : q.where.bind(q);
+ where("media.thumbnail IS NULL");
+ hasWhere = true;
+ }
+
if (fp.O.isSome(emptyLinks) && emptyLinks.value) {
const where = hasWhere ? q.andWhere.bind(q) : q.where.bind(q);
where("links.id IS NULL");
diff --git a/services/api/src/routes/admin/media/getMediaStats.controller.ts b/services/api/src/routes/admin/media/getMediaStats.controller.ts
index 261b5d4ab..9c5a668fe 100644
--- a/services/api/src/routes/admin/media/getMediaStats.controller.ts
+++ b/services/api/src/routes/admin/media/getMediaStats.controller.ts
@@ -1,9 +1,7 @@
import { pipe } from "@liexp/core/lib/fp/index.js";
import { AddEndpoint, Endpoints } from "@liexp/shared/lib/endpoints/index.js";
-import { sequenceS } from "fp-ts/lib/Apply.js";
import * as TE from "fp-ts/lib/TaskEither.js";
-import { getOrphanMediaFlow } from "#flows/media/getOrphanMedia.flow.js";
-import { getTempMediaCountFlow } from "#flows/media/getTempMediaCount.flow.js";
+import { getMediaAdminStatsFlow } from "#flows/admin/media/getMediaAdminStats.flow.js";
import { type Route } from "#routes/route.types.js";
import { authenticationHandler } from "#utils/authenticationHandler.js";
@@ -12,18 +10,16 @@ export const MakeAdminGetMediaStatsRoute: Route = (r, ctx) => {
Endpoints.Admin.Custom.GetMediaStats,
() => {
return pipe(
- sequenceS(TE.ApplicativePar)({
- orphans: getOrphanMediaFlow(ctx)(),
- temp: getTempMediaCountFlow(ctx)(),
- }),
- TE.map(({ orphans: data, temp }) => ({
+ getMediaAdminStatsFlow(ctx)(),
+ TE.map(({ orphans: data, temp, noThumbnails }) => ({
body: {
- data: { ...data, temp },
+ data: { ...data, temp, noThumbnails },
total: data.orphans.length + data.match.length,
totals: {
orphans: data.orphans.length,
match: data.match.length,
temp: temp.length,
+ noThumbnails: noThumbnails.length,
},
},
statusCode: 201,