feat: dynamic magazine category filter from published articles

Add articleCategories GraphQL query returning distinct categories from
published articles; magazine page fetches them at load time and renders
only categories that actually have content.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 17:13:08 +01:00
parent 505d59b813
commit bfd058ae58
4 changed files with 42 additions and 10 deletions

View File

@@ -1,7 +1,7 @@
import { builder } from "../builder"; import { builder } from "../builder";
import { ArticleType, ArticleListType, AdminArticleListType } from "../types/index"; import { ArticleType, ArticleListType, AdminArticleListType } from "../types/index";
import { articles, users } from "../../db/schema/index"; import { articles, users } from "../../db/schema/index";
import { eq, and, lte, desc, asc, ilike, or, count, arrayContains, type SQL } from "drizzle-orm"; import { eq, and, lte, desc, asc, ilike, or, count, arrayContains, isNotNull, type SQL } from "drizzle-orm";
import { requireAdmin } from "../../lib/acl"; import { requireAdmin } from "../../lib/acl";
import type { DB } from "../../db/connection"; import type { DB } from "../../db/connection";
@@ -74,6 +74,20 @@ builder.queryField("articles", (t) =>
}), }),
); );
builder.queryField("articleCategories", (t) =>
t.field({
type: ["String"],
resolve: async (_root, _args, ctx) => {
const rows = await ctx.db
.selectDistinct({ category: articles.category })
.from(articles)
.where(and(lte(articles.publish_date, new Date()), isNotNull(articles.category)))
.orderBy(asc(articles.category));
return rows.map((r) => r.category!);
},
}),
);
builder.queryField("article", (t) => builder.queryField("article", (t) =>
t.field({ t.field({
type: ArticleType, type: ArticleType,

View File

@@ -298,6 +298,23 @@ export async function getArticles(
}); });
} }
const ARTICLE_CATEGORIES_QUERY = gql`
query GetArticleCategories {
articleCategories
}
`;
export async function getArticleCategories(
fetchFn?: typeof globalThis.fetch,
): Promise<string[]> {
return loggedApiCall("getArticleCategories", async () => {
const data = await getGraphQLClient(fetchFn).request<{ articleCategories: string[] }>(
ARTICLE_CATEGORIES_QUERY,
);
return data.articleCategories;
});
}
const ARTICLE_BY_SLUG_QUERY = gql` const ARTICLE_BY_SLUG_QUERY = gql`
query GetArticleBySlug($slug: String!) { query GetArticleBySlug($slug: String!) {
article(slug: $slug) { article(slug: $slug) {

View File

@@ -1,4 +1,4 @@
import { getArticles } from "$lib/services"; import { getArticles, getArticleCategories } from "$lib/services";
const LIMIT = 24; const LIMIT = 24;
@@ -9,6 +9,9 @@ export async function load({ fetch, url }) {
const page = Math.max(1, parseInt(url.searchParams.get("page") || "1", 10)); const page = Math.max(1, parseInt(url.searchParams.get("page") || "1", 10));
const offset = (page - 1) * LIMIT; const offset = (page - 1) * LIMIT;
const result = await getArticles({ search, sortBy: sort, category, offset, limit: LIMIT }, fetch); const [result, categories] = await Promise.all([
return { ...result, search, sort, category, page, limit: LIMIT }; getArticles({ search, sortBy: sort, category, offset, limit: LIMIT }, fetch),
getArticleCategories(fetch),
]);
return { ...result, search, sort, category, page, limit: LIMIT, categories };
} }

View File

@@ -82,12 +82,10 @@
value={data.category ?? "all"} value={data.category ?? "all"}
options={[ options={[
{ value: "all", label: $_("magazine.categories.all") }, { value: "all", label: $_("magazine.categories.all") },
{ value: "photography", label: $_("magazine.categories.photography") }, ...data.categories.map((c) => ({
{ value: "production", label: $_("magazine.categories.production") }, value: c,
{ value: "interview", label: $_("magazine.categories.interview") }, label: $_(`magazine.categories.${c}`, { default: c }),
{ value: "psychology", label: $_("magazine.categories.psychology") }, })),
{ value: "trends", label: $_("magazine.categories.trends") },
{ value: "spotlight", label: $_("magazine.categories.spotlight") },
]} ]}
onchange={(v) => setParam("category", v)} onchange={(v) => setParam("category", v)}
/> />