import { fetchGraphQL } from './graphql-client.js'; import type { ProfileArticle } from '../types/graphql.js'; //кэширование import { cache } from '@lib/cache/manager.js'; import { CACHE_TTL } from '@lib/cache/cache-ttl'; export interface AnewsPost { title: string; uri: string; date: string; } export async function getLatestPosts(first = 14, after = null) { // Создаем уникальный ключ для кэша const cacheKey = `latest-posts:${first}:${after || 'first-page'}`; return await cache.wrap( cacheKey, async () => { const query = ` query GetLatestProfileArticles($first: Int!, $after: String) { profileArticles( first: $first after: $after where: { orderby: { field: DATE, order: DESC } } ) { pageInfo { hasNextPage endCursor } edges { cursor node { id databaseId title uri date featuredImage { node { sourceUrl(size: LARGE) altText } } author { node { id name firstName lastName avatar { url } uri } } # Соавторы как массив coauthors { id name firstName lastName url description } categories { nodes { id name color slug uri databaseId } } tags { nodes { id name slug uri } } } } } } `; const data = await fetchGraphQL(query, { first, after }); // Преобразуем edges в nodes const posts = data.profileArticles?.edges?.map(edge => edge.node) || []; return { posts, pageInfo: data.profileArticles?.pageInfo || { hasNextPage: false, endCursor: null } }; }, { ttl: CACHE_TTL.POSTS } // из конфигурации ); } export async function getPostsByCategory(slug, first = 14, after = null) { // Создаем уникальный ключ для кэша const cacheKey = `category-posts:${slug}:${first}:${after || 'first-page'}`; return await cache.wrap( cacheKey, async () => { const query = ` query GetPostsByCategory($first: Int!, $after: String, $slug: ID!) { category(id: $slug, idType: SLUG) { name contentNodes( first: $first after: $after where: { contentTypes: [PROFILE_ARTICLE, ANEW] orderby: { field: DATE, order: DESC } } ) { pageInfo { hasNextPage endCursor } edges { cursor node { __typename id databaseId uri date ... on NodeWithTitle { title } ... on NodeWithFeaturedImage { featuredImage { node { sourceUrl(size: LARGE) altText } } } # Для ваших кастомных типов ... on ProfileArticle { categories { nodes { name slug color } } } ... on ANew { categories { nodes { name slug color } } } ... on NodeWithAuthor { author { node { name avatar { url } } } } # Coauthors для ProfileArticle (без description) ... on ProfileArticle { coauthors { id name firstName lastName url } } # Coauthors для ANew (без description) ... on ANew { coauthors { id name firstName lastName url } } } } } } } `; const data = await fetchGraphQL(query, { first, after, slug }); // Обрабатываем посты const posts = data.category?.contentNodes?.edges?.map(edge => { const node = edge.node; // Приводим coauthors к единому формату (если нужно) if (node.coauthors) { node.coauthors = node.coauthors.map(author => ({ id: author.id, name: author.name || '', firstName: author.firstName || '', lastName: author.lastName || '', url: author.url || '' })); } return node; }) || []; return { posts, pageInfo: data.category?.contentNodes?.pageInfo || { hasNextPage: false, endCursor: null }, categoryName: data.category?.name || '' }; }, { ttl: CACHE_TTL.POSTS } ); } export async function getPostsByTag(slug, first = 14, after = null) { // Создаем уникальный ключ для кэша const cacheKey = `tag-posts:${slug}:${first}:${after || 'first-page'}`; return await cache.wrap( cacheKey, async () => { const query = ` query GetPostsByTag($first: Int!, $after: String, $slug: ID!) { tag(id: $slug, idType: SLUG) { id databaseId name slug count description contentNodes( first: $first after: $after where: { contentTypes: [PROFILE_ARTICLE, ANEW] orderby: { field: DATE, order: DESC } } ) { pageInfo { hasNextPage endCursor } edges { cursor node { __typename id databaseId uri date ... on NodeWithTitle { title } ... on NodeWithFeaturedImage { featuredImage { node { sourceUrl(size: LARGE) altText } } } ... on NodeWithAuthor { author { node { name avatar { url } } } } ... on ProfileArticle { categories { nodes { name slug } } } ... on ANew { categories { nodes { name slug } } } ... on ProfileArticle { coauthors { id name } } ... on ANew { coauthors { id name } } } } } } } `; console.log('Fetching with:', { first, after, slug }); // Добавим лог параметров const data = await fetchGraphQL(query, { first, after, slug }); // Проверяем структуру data if (!data?.tag) { console.log('Tag not found'); return { posts: [], pageInfo: { hasNextPage: false, endCursor: null }, tagName: slug, tagSlug: slug, tagCount: 0, tagDescription: '' }; } // Обрабатываем посты const posts = data.tag?.contentNodes?.edges?.map(edge => { const node = edge.node; // Приводим данные к единому формату return { ...node, // Убеждаемся, что coauthors всегда массив coauthors: node.coauthors || [], // Убеждаемся, что categories всегда есть categories: node.categories || { nodes: [] } }; }) || []; return { posts, pageInfo: data.tag?.contentNodes?.pageInfo || { hasNextPage: false, endCursor: null }, tagName: data.tag?.name || slug, tagSlug: data.tag?.slug || slug, tagCount: data.tag?.count || 0, tagDescription: data.tag?.description || '' }; }, { ttl: CACHE_TTL.POSTS } ); } //последние новости (кэшированная версия) export async function getLatestAnews(count = 12): Promise { const cacheKey = `latest-anews:${count}`; return await cache.wrap( cacheKey, async () => { const query = ` query GetAnews($count: Int!) { aNews(first: $count, where: {orderby: {field: DATE, order: DESC}}) { nodes { title uri date } } } `; const data = await fetchGraphQL(query, { count }); return data.aNews?.nodes || []; }, { ttl: CACHE_TTL.POSTS } ); } // Получить ProfileArticle по databaseId export async function getProfileArticleById(databaseId) { const query = ` query GetProfileArticleById($id: ID!) { profileArticle(id: $id, idType: DATABASE_ID) { id databaseId title secondaryTitle content excerpt uri slug date modified status featuredImage { node { id sourceUrl altText caption description mediaDetails { width height } } } author { node { id name firstName lastName avatar { url } description uri } } # Соавторы как массив coauthors { id name firstName lastName url description } categories { nodes { id name slug uri description } } tags { nodes { id name slug uri } } } } `; const data = await fetchGraphQL(query, { id: databaseId }); return data?.profileArticle || null; } /** * Получить Anews пост по databaseId */ export async function getAnewById(databaseId: number): Promise { const cacheKey = `anews:${databaseId}`; return await cache.wrap( cacheKey, async () => { const query = ` query GetAnewById($id: ID!) { aNew(id: $id, idType: DATABASE_ID) { id databaseId title content excerpt uri slug date modified status featuredImage { node { id sourceUrl altText caption mediaDetails { width height } } } author { node { id name firstName lastName avatar { url } description uri } } categories { nodes { id name slug uri description } } tags { nodes { id name slug uri } } } } `; try { const data = await fetchGraphQL(query, { id: databaseId }); return data?.aNew || null; } catch (error) { console.error(`Error fetching Anews post with ID ${databaseId}:`, error); return null; } }, { ttl: CACHE_TTL.POST_DETAIL } // Можно использовать отдельный TTL для деталей постов ); } /** * Получить тег по slug */ export async function getTagBySlug(slug) { const query = ` query GetTagBySlug($slug: ID!) { tag(id: $slug, idType: SLUG) { id databaseId name slug description count } } `; try { const data = await fetchGraphQL(query, { slug }); return data?.tag || null; } catch (error) { console.error('Error fetching tag:', error); return null; } } /** * Получить тег с постами по slug с пагинацией */ export async function getTagWithPostsPaginated( tagSlug: string, perPage: number = 12, page: number = 1 ) { const offset = (page - 1) * perPage; const query = ` query GetTagWithPostsPaginated($slug: ID!, $first: Int!, $offset: Int!) { tag(id: $slug, idType: SLUG) { id databaseId name slug description count seo { title metaDesc canonical opengraphTitle opengraphDescription opengraphImage { sourceUrl } } posts( first: $first offset: $offset where: { orderby: { field: DATE, order: DESC } } ) { nodes { id databaseId title excerpt uri slug date modified featuredImage { node { sourceUrl(size: MEDIUM_LARGE) altText caption mediaDetails { width height } } } author { node { id name firstName lastName avatar { url } } } categories { nodes { id name slug uri } } tags { nodes { id name slug } } seo { title metaDesc } } pageInfo { offsetPagination { total } } } } } `; try { const data = await fetchGraphQL(query, { slug: tagSlug, first: perPage, offset }); const tag = data?.tag; if (!tag) { return { tag: null, posts: [], total: 0, totalPages: 0, currentPage: page, hasNext: false, hasPrevious: false }; } const posts = tag.posts?.nodes || []; const total = tag.posts?.pageInfo?.offsetPagination?.total || 0; const totalPages = Math.ceil(total / perPage); return { tag, posts, total, totalPages, currentPage: page, hasNext: page < totalPages, hasPrevious: page > 1, perPage }; } catch (error) { console.error('Error fetching tag with posts:', error); return { tag: null, posts: [], total: 0, totalPages: 0, currentPage: page, hasNext: false, hasPrevious: false }; } } /** * Получить общее количество постов для всех тегов */ export async function getAllTagsWithCount(first = 100) { const query = ` query GetAllTagsWithCount($first: Int!) { tags(first: $first, where: { hideEmpty: true }) { nodes { id databaseId name slug count posts(first: 1) { pageInfo { offsetPagination { total } } } } } } `; const data = await fetchGraphQL(query, { first }); return data?.tags?.nodes || []; }