mirror of
https://github.com/SamyRai/tercul-frontend.git
synced 2025-12-27 04:51:34 +00:00
- Add Author, Language, Work Type, Date Range filters - Implement Save Search Preferences - Add backend routes for search and filtering with client-side pagination support
119 lines
3.5 KiB
TypeScript
119 lines
3.5 KiB
TypeScript
import { Router } from "express";
|
|
import type { Request } from "express";
|
|
import { graphqlClient } from "../lib/graphqlClient";
|
|
import { respondWithError } from "../lib/error";
|
|
import { gql } from "graphql-request";
|
|
import type { Work } from "../../shared/generated/graphql";
|
|
|
|
const router = Router();
|
|
|
|
interface GqlRequest extends Request {
|
|
gql?: typeof graphqlClient;
|
|
}
|
|
|
|
// Define a type that includes fields we need for filtering
|
|
interface WorkWithFilters extends Pick<Work, "id" | "name" | "language" | "createdAt"> {
|
|
tags?: { id: string; name: string }[];
|
|
authors?: { id: string; name: string }[];
|
|
}
|
|
|
|
const WORKS_WITH_FILTERS_QUERY = gql`
|
|
query WorksWithFilters($search: String, $limit: Int) {
|
|
works(search: $search, limit: $limit) {
|
|
id
|
|
name
|
|
language
|
|
createdAt
|
|
tags {
|
|
id
|
|
name
|
|
}
|
|
authors {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
}
|
|
`;
|
|
|
|
// GET /api/filter
|
|
router.get("/", async (req: GqlRequest, res) => {
|
|
try {
|
|
const q = req.query.q as string | undefined;
|
|
const language = req.query.language as string | undefined;
|
|
const type = req.query.type as string | undefined;
|
|
const yearStart = req.query.yearStart ? parseInt(req.query.yearStart as string) : undefined;
|
|
const yearEnd = req.query.yearEnd ? parseInt(req.query.yearEnd as string) : undefined;
|
|
const tags = req.query.tags ? (req.query.tags as string).split(",") : undefined;
|
|
const authorIds = req.query.authorIds ? (req.query.authorIds as string).split(",") : undefined;
|
|
|
|
// We ignore page param because we are doing client-side pagination for now,
|
|
// returning all matching results (up to limit).
|
|
// const page = req.query.page ? parseInt(req.query.page as string) : 1;
|
|
|
|
const client = req.gql || graphqlClient;
|
|
|
|
// Fetch a larger set to filter in memory.
|
|
const variables = {
|
|
search: q,
|
|
limit: 1000,
|
|
};
|
|
|
|
const response = await client.request<{ works: WorkWithFilters[] }>(
|
|
WORKS_WITH_FILTERS_QUERY,
|
|
variables
|
|
);
|
|
|
|
let filteredWorks = response.works;
|
|
|
|
// Filter by language
|
|
if (language) {
|
|
filteredWorks = filteredWorks.filter(w => w.language === language);
|
|
}
|
|
|
|
// Filter by type
|
|
if (type) {
|
|
filteredWorks = filteredWorks.filter(w => {
|
|
const typeLower = type.toLowerCase();
|
|
const hasTag = w.tags?.some(t => t.name.toLowerCase().includes(typeLower));
|
|
return hasTag;
|
|
});
|
|
}
|
|
|
|
// Filter by year
|
|
if (yearStart !== undefined || yearEnd !== undefined) {
|
|
filteredWorks = filteredWorks.filter(w => {
|
|
const year = new Date(w.createdAt).getFullYear();
|
|
if (yearStart !== undefined && year < yearStart) return false;
|
|
if (yearEnd !== undefined && year > yearEnd) return false;
|
|
return true;
|
|
});
|
|
}
|
|
|
|
// Filter by tags
|
|
if (tags && tags.length > 0) {
|
|
filteredWorks = filteredWorks.filter(w => {
|
|
if (!w.tags) return false;
|
|
const workTagIds = w.tags.map(t => t.id);
|
|
return tags.every(tagId => workTagIds.includes(tagId));
|
|
});
|
|
}
|
|
|
|
// Filter by authors
|
|
if (authorIds && authorIds.length > 0) {
|
|
filteredWorks = filteredWorks.filter(w => {
|
|
if (!w.authors) return false;
|
|
const workAuthorIds = w.authors.map(a => a.id);
|
|
return authorIds.some(authorId => workAuthorIds.includes(authorId));
|
|
});
|
|
}
|
|
|
|
// Return all results for client-side pagination
|
|
res.json(filteredWorks);
|
|
} catch (error) {
|
|
respondWithError(res, error, "Failed to filter works");
|
|
}
|
|
});
|
|
|
|
export default router;
|