2025-12-11 01:12:45 +03:00
|
|
|
|
import { fetchGraphQL } from './graphql-client.js';
|
|
|
|
|
|
import type { ProfileArticle } from '../types/graphql.js';
|
|
|
|
|
|
|
2025-12-14 23:03:30 +03:00
|
|
|
|
//кэширование
|
|
|
|
|
|
import { cache } from '@lib/cache/manager.js';
|
|
|
|
|
|
import { CACHE_TTL } from '@lib/cache/cache-ttl';
|
|
|
|
|
|
|
2025-12-13 23:29:25 +03:00
|
|
|
|
export interface AnewsPost {
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
uri: string;
|
|
|
|
|
|
date: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-11 01:12:45 +03:00
|
|
|
|
export async function getLatestPosts(first = 12, after = null) {
|
2025-12-14 23:03:30 +03:00
|
|
|
|
// Создаем уникальный ключ для кэша
|
|
|
|
|
|
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
|
2025-12-11 01:12:45 +03:00
|
|
|
|
}
|
2025-12-14 23:03:30 +03:00
|
|
|
|
edges {
|
|
|
|
|
|
cursor
|
2025-12-11 01:12:45 +03:00
|
|
|
|
node {
|
|
|
|
|
|
id
|
2025-12-14 23:03:30 +03:00
|
|
|
|
databaseId
|
|
|
|
|
|
title
|
2025-12-11 01:12:45 +03:00
|
|
|
|
uri
|
2025-12-14 23:03:30 +03:00
|
|
|
|
date
|
|
|
|
|
|
featuredImage {
|
|
|
|
|
|
node {
|
|
|
|
|
|
sourceUrl(size: LARGE)
|
|
|
|
|
|
altText
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
author {
|
|
|
|
|
|
node {
|
|
|
|
|
|
id
|
|
|
|
|
|
name
|
|
|
|
|
|
firstName
|
|
|
|
|
|
lastName
|
|
|
|
|
|
avatar {
|
|
|
|
|
|
url
|
|
|
|
|
|
}
|
|
|
|
|
|
uri
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
categories {
|
|
|
|
|
|
nodes {
|
|
|
|
|
|
id
|
|
|
|
|
|
name
|
|
|
|
|
|
slug
|
|
|
|
|
|
uri
|
|
|
|
|
|
databaseId
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
tags {
|
|
|
|
|
|
nodes {
|
|
|
|
|
|
id
|
|
|
|
|
|
name
|
|
|
|
|
|
slug
|
|
|
|
|
|
uri
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-11 01:12:45 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-14 23:03:30 +03:00
|
|
|
|
`;
|
2025-12-11 01:12:45 +03:00
|
|
|
|
|
2025-12-14 23:03:30 +03:00
|
|
|
|
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 } // из конфигурации
|
|
|
|
|
|
);
|
2025-12-11 01:12:45 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-14 23:03:30 +03:00
|
|
|
|
//последние новости (кэшированная версия)
|
2025-12-11 01:12:45 +03:00
|
|
|
|
export async function getLatestAnews(count = 12): Promise<AnewsPost[]> {
|
2025-12-14 23:03:30 +03:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-11 01:12:45 +03:00
|
|
|
|
}
|
2025-12-14 23:03:30 +03:00
|
|
|
|
`;
|
2025-12-11 01:12:45 +03:00
|
|
|
|
|
2025-12-14 23:03:30 +03:00
|
|
|
|
const data = await fetchGraphQL(query, { count });
|
|
|
|
|
|
return data.aNews?.nodes || [];
|
|
|
|
|
|
},
|
|
|
|
|
|
{ ttl: CACHE_TTL.POSTS }
|
|
|
|
|
|
);
|
2025-12-13 23:29:25 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Получить ProfileArticle по databaseId
|
|
|
|
|
|
export async function getProfileArticleById(databaseId) {
|
|
|
|
|
|
const query = `
|
|
|
|
|
|
query GetProfileArticleById($id: ID!) {
|
|
|
|
|
|
profileArticle(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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
const data = await fetchGraphQL(query, { id: databaseId });
|
|
|
|
|
|
return data?.profileArticle || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-22 00:00:07 +03:00
|
|
|
|
/**
|
|
|
|
|
|
* Получить Anews пост по databaseId
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function getAnewsById(databaseId: number): Promise<SingleAnewsPost | null> {
|
|
|
|
|
|
const cacheKey = `anews:${databaseId}`;
|
|
|
|
|
|
|
|
|
|
|
|
return await cache.wrap(
|
|
|
|
|
|
cacheKey,
|
|
|
|
|
|
async () => {
|
|
|
|
|
|
const query = `
|
|
|
|
|
|
query GetAnewsById($id: ID!) {
|
|
|
|
|
|
aNews(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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
seo {
|
|
|
|
|
|
title
|
|
|
|
|
|
metaDesc
|
|
|
|
|
|
canonical
|
|
|
|
|
|
opengraphTitle
|
|
|
|
|
|
opengraphDescription
|
|
|
|
|
|
opengraphImage {
|
|
|
|
|
|
sourceUrl
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const data = await fetchGraphQL(query, { id: databaseId });
|
|
|
|
|
|
return data?.aNews || null;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`Error fetching Anews post with ID ${databaseId}:`, error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{ ttl: CACHE_TTL.POST_DETAIL } // Можно использовать отдельный TTL для деталей постов
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-13 23:29:25 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Получить тег по 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-12-14 23:03:30 +03:00
|
|
|
|
* Получить тег с постами по slug с пагинацией
|
2025-12-13 23:29:25 +03:00
|
|
|
|
*/
|
2025-12-14 23:03:30 +03:00
|
|
|
|
export async function getTagWithPostsPaginated(
|
|
|
|
|
|
tagSlug: string,
|
|
|
|
|
|
perPage: number = 12,
|
|
|
|
|
|
page: number = 1
|
|
|
|
|
|
) {
|
2025-12-13 23:29:25 +03:00
|
|
|
|
const offset = (page - 1) * perPage;
|
|
|
|
|
|
|
|
|
|
|
|
const query = `
|
2025-12-14 23:03:30 +03:00
|
|
|
|
query GetTagWithPostsPaginated($slug: ID!, $first: Int!, $offset: Int!) {
|
2025-12-13 23:29:25 +03:00
|
|
|
|
tag(id: $slug, idType: SLUG) {
|
|
|
|
|
|
id
|
|
|
|
|
|
databaseId
|
|
|
|
|
|
name
|
|
|
|
|
|
slug
|
|
|
|
|
|
description
|
|
|
|
|
|
count
|
|
|
|
|
|
seo {
|
|
|
|
|
|
title
|
|
|
|
|
|
metaDesc
|
|
|
|
|
|
canonical
|
2025-12-14 23:03:30 +03:00
|
|
|
|
opengraphTitle
|
|
|
|
|
|
opengraphDescription
|
|
|
|
|
|
opengraphImage {
|
|
|
|
|
|
sourceUrl
|
|
|
|
|
|
}
|
2025-12-13 23:29:25 +03:00
|
|
|
|
}
|
|
|
|
|
|
posts(
|
|
|
|
|
|
first: $first
|
|
|
|
|
|
offset: $offset
|
|
|
|
|
|
where: {
|
2025-12-14 23:03:30 +03:00
|
|
|
|
orderby: {
|
|
|
|
|
|
field: DATE,
|
|
|
|
|
|
order: DESC
|
|
|
|
|
|
}
|
2025-12-13 23:29:25 +03:00
|
|
|
|
}
|
|
|
|
|
|
) {
|
|
|
|
|
|
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) {
|
2025-12-14 23:03:30 +03:00
|
|
|
|
console.error('Error fetching tag with posts:', error);
|
2025-12-13 23:29:25 +03:00
|
|
|
|
return {
|
|
|
|
|
|
tag: null,
|
|
|
|
|
|
posts: [],
|
|
|
|
|
|
total: 0,
|
|
|
|
|
|
totalPages: 0,
|
|
|
|
|
|
currentPage: page,
|
|
|
|
|
|
hasNext: false,
|
|
|
|
|
|
hasPrevious: false
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-14 23:03:30 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-13 23:29:25 +03:00
|
|
|
|
/**
|
|
|
|
|
|
* Получить общее количество постов для всех тегов
|
|
|
|
|
|
*/
|
|
|
|
|
|
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 || [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|