From e5af3fcfd6311a3c1b8ff2e4b47a9733c04030c4 Mon Sep 17 00:00:00 2001 From: Profile Profile Date: Mon, 2 Mar 2026 23:10:31 +0300 Subject: [PATCH] add endpoint authors --- src/components/ContentGrid.astro | 125 ++++++---- src/components/NewsSingle.astro | 15 +- src/lib/api/authors.ts | 370 ++++++++++++++++++++++++++++ src/lib/api/posts.ts | 2 + src/lib/cache/cache-ttl.ts | 3 +- src/pages/author/[slug]/index.astro | 103 +++++--- src/pages/load-more-posts.astro | 10 +- 7 files changed, 525 insertions(+), 103 deletions(-) create mode 100644 src/lib/api/authors.ts diff --git a/src/components/ContentGrid.astro b/src/components/ContentGrid.astro index f01cc99..17c33c4 100644 --- a/src/components/ContentGrid.astro +++ b/src/components/ContentGrid.astro @@ -23,19 +23,24 @@ const { perLoad = 11 } = Astro.props; -// Используем perLoad везде const loadMoreConfig = { type, slug, - perLoad // Теперь используем perLoad вместо first + perLoad }; function getCoauthorsNames(coauthors: any[]): string { if (!coauthors || coauthors.length === 0) return ''; + return coauthors - .map((coauthor: any) => coauthor?.node?.name || coauthor?.name) + .map((coauthor: any) => { + const name = coauthor?.node?.name || coauthor?.name; + const nickname = coauthor?.node?.nickname || coauthor?.nickname; + + return name; // Возвращаем только имя, ссылки будут в шаблоне + }) .filter(Boolean) - .join(' '); + .join(', '); } function shouldBeLarge(index: number): boolean { @@ -55,7 +60,6 @@ function shouldBeLarge(index: number): boolean { {items.map((item, index) => { const postUrl = item.uri || `/blog/${item.databaseId}`; const postDate = new Date(item.date); - const coauthorsNames = getCoauthorsNames(item.coauthors || []); const isLarge = shouldBeLarge(index); return ( @@ -66,54 +70,83 @@ function shouldBeLarge(index: number): boolean { itemscope itemtype="https://schema.org/BlogPosting" > - -
- {item.featuredImage?.node?.sourceUrl ? ( - {item.featuredImage.node.altText - ) : ( -
- )} - - + {item.featuredImage?.node?.sourceUrl ? ( + {item.featuredImage.node.altText + ) : ( +
+ )} + + {item.categories?.nodes?.[0] && ( +
e.stopPropagation()} + > + + + )} + +
+ -
- - + - {coauthorsNames && ( - - )} -
+ {item.coauthors && item.coauthors.length > 0 && ( + + )}
- +

diff --git a/src/components/NewsSingle.astro b/src/components/NewsSingle.astro index 84e1808..7e6e460 100644 --- a/src/components/NewsSingle.astro +++ b/src/components/NewsSingle.astro @@ -72,7 +72,7 @@ const { post, pageInfo } = Astro.props; {/* Блок с тегами */} {post.tags?.nodes && post.tags.nodes.length > 0 && (
- Теги: + Метки:
{post.tags.nodes.map((tag) => ( { + const query = ` + query GetPostsByCoauthorName($first: Int!, $after: String, $coauthorName: String!) { + # Информация об авторе + users(where: {search: $coauthorName}) { + nodes { + databaseId + name + nicename + email + avatar { + url + } + description + posts { + pageInfo { + total + } + } + } + } + + # Посты автора + contentNodes( + first: $first + after: $after + where: { + contentTypes: [PROFILE_ARTICLE, ANEW] + coauthorName: $coauthorName + orderby: { field: DATE, order: DESC } + } + ) { + pageInfo { + hasNextPage + endCursor + } + nodes { + __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 + ... on ProfileArticle { + coauthors { + id + name + firstName + lastName + url + nicename + } + } + + # Coauthors для ANew + ... on ANew { + coauthors { + id + name + firstName + lastName + url + nicename + } + } + } + } + } + `; + + const data = await fetchGraphQL(query, { + first, + after, + coauthorName + }); + + // Находим автора (первый результат поиска) + const author = data.users?.nodes?.[0]; + + if (!author) { + return { + author: null, + posts: [], + pageInfo: { hasNextPage: false, endCursor: null }, + totalPosts: 0 + }; + } + + // Обрабатываем посты + const posts = data.contentNodes?.nodes?.map(node => { + // Приводим coauthors к единому формату + if (node.coauthors) { + node.coauthors = node.coauthors.map(coauthor => ({ + id: coauthor.id, + nickname: coauthor.nickname, + name: coauthor.name || '', + firstName: coauthor.firstName || '', + lastName: coauthor.lastName || '', + url: coauthor.url || '' + })); + } + return node; + }) || []; + + return { + author: { + id: author.databaseId, + name: author.name, + login: author.nicename, + email: author.email, + avatar: author.avatar?.url, + description: author.description, + totalPosts: author.posts?.pageInfo?.total || posts.length + }, + posts, + pageInfo: data.contentNodes?.pageInfo || { + hasNextPage: false, + endCursor: null + }, + authorName: coauthorName + }; + }, + { ttl: CACHE_TTL.POSTS } + ); +} + +// все посты автора +export async function getPostsByCoauthorLogin(login, first = 14, after = null) { + // Создаем уникальный ключ для кэша + const cacheKey = `coauthor-posts-by-login:${login}:${first}:${after || 'first-page'}`; + + return await cache.wrap( + cacheKey, + async () => { + const query = ` + query GetPostsByCoauthorLogin($first: Int!, $after: String, $login: String!) { + contentNodes( + first: $first + after: $after + where: { + contentTypes: [PROFILE_ARTICLE, ANEW] + coauthorLogin: $login + orderby: { field: DATE, order: DESC } + } + ) { + pageInfo { + hasNextPage + endCursor + } + nodes { + __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 + ... on ProfileArticle { + coauthors { + id + name + firstName + lastName + url + nicename # Добавляем nicename (login) + } + } + + # Coauthors для ANew + ... on ANew { + coauthors { + id + name + firstName + lastName + url + nicename # Добавляем nicename (login) + } + } + } + } + } + `; + + const data = await fetchGraphQL(query, { + first, + after, + login + }); + + // Обрабатываем посты + const posts = data.contentNodes?.nodes?.map(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 || '', + nicename: author.nicename || '' // Добавляем login + })); + } + + return node; + }) || []; + + return { + posts, + pageInfo: data.contentNodes?.pageInfo || { + hasNextPage: false, + endCursor: null + }, + authorLogin: login + }; + }, + { ttl: CACHE_TTL.POSTS } + ); +} + + +export async function getAuthorData(slug) { + const cacheKey = `author-data:slug:${slug}`; + + return await cache.wrap( + cacheKey, + async () => { + const baseUrl = import.meta.env.WP_REST_BASE_URL.replace(/\/$/, ''); + + try { + const response = await fetch(`${baseUrl}/author/${slug}`); + + if (!response.ok) { + if (response.status === 404) { + return null; + } + throw new Error(`HTTP error: ${response.status}`); + } + + const author = await response.json(); + + // Форматируем ответ + return { + id: author.slug || author.id, + name: author.name, + firstName: author.firstName, + lastName: author.lastName, + avatar: author.photo || author.avatar, + avatar_sizes: author.photo_sizes || {}, + bio: author.description, + social: author.social || {}, + url: author.url, + type: author.type, + }; + } catch (error) { + console.error(`❌ Failed to fetch author ${slug}:`, error); + return null; + } + }, + { ttl: CACHE_TTL.AUTHOR } + ); +} + + + + + diff --git a/src/lib/api/posts.ts b/src/lib/api/posts.ts index a14a46f..6fff56e 100644 --- a/src/lib/api/posts.ts +++ b/src/lib/api/posts.ts @@ -398,6 +398,8 @@ export async function getPostsByTag(slug, first = 14, after = null) { + + //последние новости (кэшированная версия) export async function getLatestAnews(count = 12): Promise { const cacheKey = `latest-anews:${count}`; diff --git a/src/lib/cache/cache-ttl.ts b/src/lib/cache/cache-ttl.ts index 91b1cf1..560bc86 100644 --- a/src/lib/cache/cache-ttl.ts +++ b/src/lib/cache/cache-ttl.ts @@ -3,7 +3,8 @@ */ export const CACHE_TTL = { TAXONOMY: parseInt(import.meta.env.CACHE_TAXONOMY_TTL || '3600'), - POSTS: parseInt(import.meta.env.CACHE_POST_TTL || '1800') + POSTS: parseInt(import.meta.env.CACHE_POST_TTL || '1800'), + AUTHOR: parseInt(import.meta.env.CACHE_AUTHOR_TTL || '8') } as const; // Для отключения кэша diff --git a/src/pages/author/[slug]/index.astro b/src/pages/author/[slug]/index.astro index 5e3e566..75df7f8 100644 --- a/src/pages/author/[slug]/index.astro +++ b/src/pages/author/[slug]/index.astro @@ -1,56 +1,77 @@ --- -// pages/author/[slug]/index.astro -import { wpClient } from '@lib/wp-client'; +import MainLayout from '@layouts/MainLayout.astro'; +import { getAuthorData, getPostsByCoauthorLogin } from '@lib/api/authors'; + +import ContentGrid from '@components/ContentGrid.astro'; export const prerender = false; - const { slug } = Astro.params; -// Функция для получения данных автора -async function getAuthorData(authorSlug) { - try { - // Используем ваш кастомный эндпоинт или стандартный WP endpoint - const data = await wpClient.get(`my/v1/author-posts/${authorSlug}/1`); - - if (!data) { - // Если кастомный эндпоинт не работает, пробуем стандартный - const users = await wpClient.get('wp/v2/users', { slug: authorSlug }); - if (users && users.length > 0) { - return { author: users[0], posts: [] }; - } - return null; - } - - return data; - } catch (error) { - console.error('Error fetching author:', error); - return null; - } -} +const author = await getAuthorData(slug); -const authorData = await getAuthorData(slug); +const data = await getPostsByCoauthorLogin(slug); +const posts = data.posts; // Если автор не найден - 404 //if (!authorData) { // return Astro.redirect('/404'); //} -const { author, posts } = authorData; --- -

Author: {author.name || slug}

+ + +{author && ( +
+)} + + + + -{posts && posts.length > 0 ? ( -
-

Статьи автора:

- -
-) : ( -

У автора пока нет статей

-)} \ No newline at end of file diff --git a/src/pages/load-more-posts.astro b/src/pages/load-more-posts.astro index 819a81d..ff07433 100644 --- a/src/pages/load-more-posts.astro +++ b/src/pages/load-more-posts.astro @@ -1,5 +1,7 @@ --- import ContentGrid from '@components/ContentGrid.astro'; +import { getLatestPosts, getPostsByCategory, getAuthorPosts, getPostsByTag } from '../lib/api/posts'; +import { getPostsByCoauthorLogin } from '../lib/api/authors'; export const prerender = false; @@ -22,12 +24,6 @@ const { startIndex = 0 } = body; -console.log('📥 Load more request:', { perLoad, after, type, slug, startIndex }); - - -// Импортируем функции для получения данных -const { getLatestPosts, getPostsByCategory, getAuthorPosts, getPostsByTag } = - await import('../lib/api/posts'); let result; @@ -39,7 +35,7 @@ switch (type) { break; case 'author': if (!slug) throw new Error('Slug required for author'); - result = await getAuthorPosts(slug, perLoad, after); // Используем perLoad + result = await getPostsByCoauthorLogin(slug, perLoad, after); // Используем perLoad break; case 'tag': if (!slug) throw new Error('Slug required for tag');