From 92dc8ae213a6b03a7980533e0bafe34c0c598a13 Mon Sep 17 00:00:00 2001 From: sergeyyarkov Date: Tue, 24 Feb 2026 15:42:47 +0300 Subject: [PATCH] rss --- src/graphql/gql.ts | 6 ++++++ src/graphql/graphql.ts | 32 ++++++++++++++++++++++++++++++++ src/pages/[lang]/rss.xml.ts | 35 +++++++++++++++++++++++++++++++++++ src/pages/rss.xml.ts | 29 ----------------------------- src/queries/article.ts | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 105 insertions(+), 29 deletions(-) create mode 100644 src/pages/[lang]/rss.xml.ts delete mode 100644 src/pages/rss.xml.ts diff --git a/src/graphql/gql.ts b/src/graphql/gql.ts index c2f6a11..617b032 100644 --- a/src/graphql/gql.ts +++ b/src/graphql/gql.ts @@ -17,6 +17,7 @@ import * as types from './graphql'; type Documents = { "\n\t\tquery LatestAppealByClientIpQuery($client_ip: String!, $limit: Int!) {\n\t\t\tappeal(filter: { client_ip: { _eq: $client_ip } }, sort: [\"-date_created\"], limit: $limit) {\n\t\t\t\tclient_ip\n\t\t\t\tdate_created\n\t\t\t}\n\t\t}\n\t": typeof types.LatestAppealByClientIpQueryDocument, "\n\t\tmutation CreateAppealQuery(\n\t\t\t$name: String!\n\t\t\t$email: String!\n\t\t\t$subject: String!\n\t\t\t$message: String!\n\t\t\t$client_ip: String!\n\t\t) {\n\t\t\tcreate_appeal_item(\n\t\t\t\tdata: { name: $name, email: $email, subject: $subject, message: $message, client_ip: $client_ip }\n\t\t\t) {\n\t\t\t\tid\n\t\t\t}\n\t\t}\n\t": typeof types.CreateAppealQueryDocument, + "\n\t\tquery ArticleRSS($lang: String!) {\n\t\t\tarticle_translations(filter: { languages_code: { code: { _contains: $lang } }, status: { _eq: \"published\" } }) {\n\t\t\t\ttitle\n\t\t\t\tslug\n\t\t\t\ttags {\n\t\t\t\t\ttag_id {\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tauthor {\n\t\t\t\t\tfirst_name\n\t\t\t\t\tlast_name\n\t\t\t\t\temail\n\t\t\t\t}\n\t\t\t\tdescription\n\t\t\t\tpub_date\n\t\t\t\tlanguages_code {\n\t\t\t\t\tcode\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": typeof types.ArticleRssDocument, "\n\t\tquery Article {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\tid\n\t\t\t\t\ttitle\n\t\t\t\t\ttags {\n\t\t\t\t\t\ttag_id {\n\t\t\t\t\t\t\ttitle\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdescription\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": typeof types.ArticleDocument, "\n\t\tquery RecentArticle($limit: Int!) {\n\t\t\tarticle(limit: $limit, sort: [\"-translations.pub_date\"]) {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\ttitle\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": typeof types.RecentArticleDocument, "\n\t\tquery Article_translations($slug: String!) {\n\t\t\tarticle_translations(filter: { slug: { _eq: $slug } }) {\n\t\t\t\tid\n\t\t\t\ttitle\n\t\t\t\ttags {\n\t\t\t\t\ttag_id {\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdescription\n\t\t\t\tslug\n\t\t\t\tcontent\n\t\t\t\tauthor {\n\t\t\t\t\tfirst_name\n\t\t\t\t\tlast_name\n\t\t\t\t}\n\t\t\t\tpub_date\n\t\t\t\tviews\n\t\t\t\tlanguages_code {\n\t\t\t\t\tcode\n\t\t\t\t\tname\n\t\t\t\t}\n\t\t\t\tcover_image {\n\t\t\t\t\ttitle\n\t\t\t\t\ttype\n\t\t\t\t\tfilename_disk\n\t\t\t\t}\n\t\t\t\tarticle_id {\n\t\t\t\t\ttranslations {\n\t\t\t\t\t\tslug\n\t\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\t\tcode\n\t\t\t\t\t\t\tname\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpub_date\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": typeof types.Article_TranslationsDocument, @@ -30,6 +31,7 @@ type Documents = { const documents: Documents = { "\n\t\tquery LatestAppealByClientIpQuery($client_ip: String!, $limit: Int!) {\n\t\t\tappeal(filter: { client_ip: { _eq: $client_ip } }, sort: [\"-date_created\"], limit: $limit) {\n\t\t\t\tclient_ip\n\t\t\t\tdate_created\n\t\t\t}\n\t\t}\n\t": types.LatestAppealByClientIpQueryDocument, "\n\t\tmutation CreateAppealQuery(\n\t\t\t$name: String!\n\t\t\t$email: String!\n\t\t\t$subject: String!\n\t\t\t$message: String!\n\t\t\t$client_ip: String!\n\t\t) {\n\t\t\tcreate_appeal_item(\n\t\t\t\tdata: { name: $name, email: $email, subject: $subject, message: $message, client_ip: $client_ip }\n\t\t\t) {\n\t\t\t\tid\n\t\t\t}\n\t\t}\n\t": types.CreateAppealQueryDocument, + "\n\t\tquery ArticleRSS($lang: String!) {\n\t\t\tarticle_translations(filter: { languages_code: { code: { _contains: $lang } }, status: { _eq: \"published\" } }) {\n\t\t\t\ttitle\n\t\t\t\tslug\n\t\t\t\ttags {\n\t\t\t\t\ttag_id {\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tauthor {\n\t\t\t\t\tfirst_name\n\t\t\t\t\tlast_name\n\t\t\t\t\temail\n\t\t\t\t}\n\t\t\t\tdescription\n\t\t\t\tpub_date\n\t\t\t\tlanguages_code {\n\t\t\t\t\tcode\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": types.ArticleRssDocument, "\n\t\tquery Article {\n\t\t\tarticle {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\tid\n\t\t\t\t\ttitle\n\t\t\t\t\ttags {\n\t\t\t\t\t\ttag_id {\n\t\t\t\t\t\t\ttitle\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdescription\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": types.ArticleDocument, "\n\t\tquery RecentArticle($limit: Int!) {\n\t\t\tarticle(limit: $limit, sort: [\"-translations.pub_date\"]) {\n\t\t\t\ttranslations(filter: { status: { _eq: \"published\" } }) {\n\t\t\t\t\ttitle\n\t\t\t\t\tslug\n\t\t\t\t\tpub_date\n\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\tcode\n\t\t\t\t\t\tname\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": types.RecentArticleDocument, "\n\t\tquery Article_translations($slug: String!) {\n\t\t\tarticle_translations(filter: { slug: { _eq: $slug } }) {\n\t\t\t\tid\n\t\t\t\ttitle\n\t\t\t\ttags {\n\t\t\t\t\ttag_id {\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdescription\n\t\t\t\tslug\n\t\t\t\tcontent\n\t\t\t\tauthor {\n\t\t\t\t\tfirst_name\n\t\t\t\t\tlast_name\n\t\t\t\t}\n\t\t\t\tpub_date\n\t\t\t\tviews\n\t\t\t\tlanguages_code {\n\t\t\t\t\tcode\n\t\t\t\t\tname\n\t\t\t\t}\n\t\t\t\tcover_image {\n\t\t\t\t\ttitle\n\t\t\t\t\ttype\n\t\t\t\t\tfilename_disk\n\t\t\t\t}\n\t\t\t\tarticle_id {\n\t\t\t\t\ttranslations {\n\t\t\t\t\t\tslug\n\t\t\t\t\t\tlanguages_code {\n\t\t\t\t\t\t\tcode\n\t\t\t\t\t\t\tname\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpub_date\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t": types.Article_TranslationsDocument, @@ -49,6 +51,10 @@ export function graphql(source: "\n\t\tquery LatestAppealByClientIpQuery($client * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n\t\tmutation CreateAppealQuery(\n\t\t\t$name: String!\n\t\t\t$email: String!\n\t\t\t$subject: String!\n\t\t\t$message: String!\n\t\t\t$client_ip: String!\n\t\t) {\n\t\t\tcreate_appeal_item(\n\t\t\t\tdata: { name: $name, email: $email, subject: $subject, message: $message, client_ip: $client_ip }\n\t\t\t) {\n\t\t\t\tid\n\t\t\t}\n\t\t}\n\t"): typeof import('./graphql').CreateAppealQueryDocument; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n\t\tquery ArticleRSS($lang: String!) {\n\t\t\tarticle_translations(filter: { languages_code: { code: { _contains: $lang } }, status: { _eq: \"published\" } }) {\n\t\t\t\ttitle\n\t\t\t\tslug\n\t\t\t\ttags {\n\t\t\t\t\ttag_id {\n\t\t\t\t\t\ttitle\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tauthor {\n\t\t\t\t\tfirst_name\n\t\t\t\t\tlast_name\n\t\t\t\t\temail\n\t\t\t\t}\n\t\t\t\tdescription\n\t\t\t\tpub_date\n\t\t\t\tlanguages_code {\n\t\t\t\t\tcode\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t"): typeof import('./graphql').ArticleRssDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/src/graphql/graphql.ts b/src/graphql/graphql.ts index 3f83eca..c5ec537 100644 --- a/src/graphql/graphql.ts +++ b/src/graphql/graphql.ts @@ -4353,6 +4353,13 @@ export type CreateAppealQueryMutationVariables = Exact<{ export type CreateAppealQueryMutation = { __typename?: 'Mutation', create_appeal_item?: { __typename?: 'appeal', id: string } | null }; +export type ArticleRssQueryVariables = Exact<{ + lang: Scalars['String']['input']; +}>; + + +export type ArticleRssQuery = { __typename?: 'Query', article_translations: Array<{ __typename?: 'article_translations', title: string, slug: string, description: string, pub_date: any, tags?: Array<{ __typename?: 'article_translations_tag', tag_id?: { __typename?: 'tag', title: string } | null } | null> | null, author?: { __typename?: 'directus_users', first_name?: string | null, last_name?: string | null, email?: string | null } | null, languages_code?: { __typename?: 'languages', code: string } | null }> }; + export type ArticleQueryVariables = Exact<{ [key: string]: never; }>; @@ -4449,6 +4456,31 @@ export const CreateAppealQueryDocument = new TypedDocumentString(` } } `) as unknown as TypedDocumentString; +export const ArticleRssDocument = new TypedDocumentString(` + query ArticleRSS($lang: String!) { + article_translations( + filter: {languages_code: {code: {_contains: $lang}}, status: {_eq: "published"}} + ) { + title + slug + tags { + tag_id { + title + } + } + author { + first_name + last_name + email + } + description + pub_date + languages_code { + code + } + } +} + `) as unknown as TypedDocumentString; export const ArticleDocument = new TypedDocumentString(` query Article { article { diff --git a/src/pages/[lang]/rss.xml.ts b/src/pages/[lang]/rss.xml.ts new file mode 100644 index 0000000..a704e6b --- /dev/null +++ b/src/pages/[lang]/rss.xml.ts @@ -0,0 +1,35 @@ +import { APIContext } from "astro"; +import { DEFAULT_LANGUAGE, SITE_METADATA, URL_BLOG_PREFIX } from "@root/constants"; +import { createRelativeArticleUrl } from "@root/utils"; +import { getArticleTranslationsRSSList } from "../../queries"; +import { getRssString } from "@astrojs/rss"; +import * as utils from "@i18n/utils"; + +export async function GET(context: APIContext) { + const params = context.params as { lang: LanguageKeys }; + const articles = await getArticleTranslationsRSSList(params.lang); + const rssString = await getRssString({ + site: context.site?.origin || "https://yarkov.tech", + title: SITE_METADATA.name, + description: utils.translate("site.description", params.lang), + items: articles.map((a) => { + const articleLang = a.languages_code?.code.split("-")[0] || DEFAULT_LANGUAGE; + const link = createRelativeArticleUrl({ articleLang, pubDate: a.pub_date, slug: a.slug }, URL_BLOG_PREFIX); + + return { + title: a.title, + description: a.description, + author: `${a.author?.email} (${a.author?.first_name} ${a.author?.last_name})`, + pubDate: a.pub_date, + categories: a.tags ? a.tags.filter((t) => !!t && !!t.tag_id).map((t) => t!.tag_id!.title) : [], + link, + }; + }), + }); + + return new Response(rssString, { + headers: { + "Content-Type": "application/xml", + }, + }); +} diff --git a/src/pages/rss.xml.ts b/src/pages/rss.xml.ts deleted file mode 100644 index 7c944cd..0000000 --- a/src/pages/rss.xml.ts +++ /dev/null @@ -1,29 +0,0 @@ -import rss from "@astrojs/rss"; -import { SITE_METADATA, URL_BLOG_PREFIX } from "@root/constants"; -import { createRelativeArticleUrl } from "@root/utils"; -import * as utils from "@i18n/utils"; -import { getArticleList } from "../queries"; - -// TODO: generate feed for RU, EN, ... languages -export async function GET() { - const articles = await getArticleList(); - return rss({ - title: SITE_METADATA.name, - description: utils.translate("site.description", "ru"), - site: "https://yarkov.tech", - items: articles - .filter((a) => !!a) - .map((a) => { - return { - title: a.title, - pubDate: a.pub_date, - description: `${a.description}`, - link: createRelativeArticleUrl( - { slug: a.slug, pubDate: a.pub_date, articleLang: a.languages_code?.code.split("-")[0] || "" }, - URL_BLOG_PREFIX - ), - customData: `${a.languages_code?.code}`, - }; - }), - }); -} diff --git a/src/queries/article.ts b/src/queries/article.ts index 21e5e09..a6cf1c1 100644 --- a/src/queries/article.ts +++ b/src/queries/article.ts @@ -2,6 +2,38 @@ import { graphql } from "../graphql"; import execute from "../graphql/execute"; import { Article_Translations } from "../graphql/graphql"; +export async function getArticleTranslationsRSSList(lang: string) { + const ArticleTranslationRSSQuery = graphql(` + query ArticleRSS($lang: String!) { + article_translations(filter: { languages_code: { code: { _contains: $lang } }, status: { _eq: "published" } }) { + title + slug + tags { + tag_id { + title + } + } + author { + first_name + last_name + email + } + description + pub_date + languages_code { + code + } + } + } + `); + + const result = await execute(ArticleTranslationRSSQuery, { lang }); + + if (!result.data) return []; + + return result.data.article_translations; +} + export async function getArticleList() { const ArticleQuery = graphql(` query Article {