804 lines
18 KiB
TypeScript
804 lines
18 KiB
TypeScript
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<AnewsPost[]> {
|
||
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<SingleAnewsPost | null> {
|
||
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 || [];
|
||
}
|
||
|
||
|
||
|
||
|