599 lines
12 KiB
TypeScript
599 lines
12 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: String!) {
|
||
profileArticles(
|
||
first: $first
|
||
after: $after
|
||
where: {
|
||
orderby: { field: DATE, order: DESC }
|
||
categoryName: $slug
|
||
}
|
||
) {
|
||
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
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
`;
|
||
|
||
const data = await fetchGraphQL(query, { first, after, slug });
|
||
|
||
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 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 || [];
|
||
}
|
||
|
||
|
||
|
||
|