Skip to content

Commit

Permalink
feat: add sitemap and rss support
Browse files Browse the repository at this point in the history
  • Loading branch information
Innei committed Jul 11, 2023
1 parent 27b6c0d commit fc4e375
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 1 deletion.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@
"shiki": "0.14.3",
"socket.io-client": "4.7.1",
"sonner": "0.6.1",
"tailwind-merge": "1.13.2"
"tailwind-merge": "1.13.2",
"xss": "1.0.14"
},
"devDependencies": {
"@iconify-json/material-symbols": "1.1.50",
Expand Down
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/app/api/xlog/summary/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const headers = {

export const runtime = 'edge'

export const revalidate = 60 * 60 // 1 hour

export const GET = async (req: NextRequest) => {
const query = req.nextUrl.searchParams
const cid = query.get('cid')
Expand Down
79 changes: 79 additions & 0 deletions src/app/feed/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import Markdown from 'markdown-to-jsx'
import xss from 'xss'
import type { AggregateRoot } from '@mx-space/api-client'

import { escapeXml } from '~/lib/helper.server'
import { getQueryClient } from '~/lib/query-client.server'
import { apiClient } from '~/lib/request'

export const runtime = 'edge'
export const revalidate = 60 * 60 // 1 hour

export async function GET() {
const ReactDOM = (await import('react-dom/server')).default
const queryClient = await getQueryClient()

const { author, data, url } = await queryClient.fetchQuery({
queryKey: ['rss'],
queryFn: async () => {
const path = apiClient.aggregate.proxy.feed.toString(true)
return fetch(path).then((res) => res.json())
},
})

const agg = await fetch(apiClient.aggregate.proxy.toString(true)).then(
(res) => res.json() as Promise<AggregateRoot>,
)

const { title } = agg.seo
const { avatar } = agg.user
const now = new Date()
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>${title}</title>
<link href="/atom.xml" rel="self"/>
<link href="/feed" rel="self"/>
<link href="${xss(url)}"/>
<updated>${now.toISOString()}</updated>
<id>${xss(url)}</id>
<author>
<name>${author}</name>
</author>
<generator>Mix Space CMS</generator>
<lastBuildDate>${now.toISOString()}</lastBuildDate>
<language>zh-CN</language>
<image>
<url>${xss(avatar || '')}</url>
<title>${title}</title>
<link>${xss(url)}</link>
</image>
${await Promise.all(
data.map(async (item: any) => {
return `<entry>
<title>${escapeXml(item.title)}</title>
<link href='${xss(item.link)}'/>
<id>${xss(item.link)}</id>
<published>${item.created}</published>
<updated>${item.modified}</updated>
<content type='html'><![CDATA[
${`<blockquote>该渲染由 Shiro API 生成,可能存在排版问题,最佳体验请前往:<a href='${xss(
item.link,
)}'>${xss(item.link)}</a></blockquote>
${ReactDOM.renderToString(<Markdown>{item.text}</Markdown>)}
<p style='text-align: right'>
<a href='${`${xss(item.link)}#comments`}'>看完了?说点什么呢</a>
</p>`}
]]>
</content>
</entry>
`
}),
).then((res) => res.join(''))}
</feed>`

return new Response(xml, {
headers: {
'Content-Type': 'application/xml',
},
})
}
36 changes: 36 additions & 0 deletions src/app/sitemap/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { getQueryClient } from '~/lib/query-client.server'
import { apiClient } from '~/lib/request'

export const runtime = 'edge'
export const revalidate = 60 * 60 // 1 hour

export const GET = async () => {
const queryClient = await getQueryClient()

const { data } = await queryClient.fetchQuery({
queryKey: ['sitemap'],
queryFn: async () => {
const path = apiClient.aggregate.proxy.sitemap.toString(true)
return fetch(path).then((res) => res.json())
},
})

const xml = `
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
${data
.map(
(item: any) => `<url>
<loc>${item.url}</loc>
<lastmod>${item.publishedAt || 'N/A'}</lastmod>
</url>`,
)
.join('')}
</urlset>
`.trim()
return new Response(xml, {
headers: {
'Content-Type': 'application/xml',
},
})
}
17 changes: 17 additions & 0 deletions src/lib/helper.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export function escapeXml(unsafe: string) {
return unsafe.replace(/[<>&'"]/g, (c) => {
switch (c) {
case '<':
return '&lt;'
case '>':
return '&gt;'
case '&':
return '&amp;'
case "'":
return '&apos;'
case '"':
return '&quot;'
}
return c
})
}

1 comment on commit fc4e375

@vercel
Copy link

@vercel vercel bot commented on fc4e375 Jul 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

shiro – ./

shiro-git-main-innei.vercel.app
shiro-innei.vercel.app
innei.in
springtide.vercel.app

Please sign in to comment.