✨ Add post RSS feed
This commit is contained in:
		| @@ -23,6 +23,7 @@ | |||||||
|     "axios": "^1.7.9", |     "axios": "^1.7.9", | ||||||
|     "axios-case-converter": "^1.1.1", |     "axios-case-converter": "^1.1.1", | ||||||
|     "cookies-next": "^5.0.2", |     "cookies-next": "^5.0.2", | ||||||
|  |     "feed": "^4.2.2", | ||||||
|     "next": "15.1.3", |     "next": "15.1.3", | ||||||
|     "next-nprogress-bar": "^2.4.3", |     "next-nprogress-bar": "^2.4.3", | ||||||
|     "react": "^19.0.0", |     "react": "^19.0.0", | ||||||
|   | |||||||
							
								
								
									
										72
									
								
								src/pages/posts/feed.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/pages/posts/feed.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | import { sni } from '@/services/network' | ||||||
|  | import { SnPost } from '@/services/post' | ||||||
|  | import { GetServerSideProps } from 'next' | ||||||
|  | import { Feed } from 'feed' | ||||||
|  |  | ||||||
|  | function generateFeed(posts: SnPost[]): string { | ||||||
|  |   const feed = new Feed({ | ||||||
|  |     title: 'Solar Network Posts', | ||||||
|  |     description: 'All posts on the Solar Network platform, and now you can view them via the RSS feed!', | ||||||
|  |     id: 'https://solsynth.dev/posts', | ||||||
|  |     link: 'https://solsynth.dev/posts', | ||||||
|  |     favicon: 'https://solsynth.dev/favicon.png', | ||||||
|  |     copyright: `All rights reserved ${new Date().getFullYear()} © Solsynth LLC & Post Publishers`, | ||||||
|  |     updated: new Date(posts[0].createdAt), | ||||||
|  |     generator: 'Capital', | ||||||
|  |     feedLinks: { | ||||||
|  |       json: 'https://example.com/json', | ||||||
|  |       atom: 'https://example.com/atom', | ||||||
|  |     }, | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   for (const p of posts) { | ||||||
|  |     feed.addItem({ | ||||||
|  |       id: p.id.toString(), | ||||||
|  |       title: p.body.title ?? `Post #${p.id}`, | ||||||
|  |       description: p.body.description, | ||||||
|  |       link: | ||||||
|  |         p.alias && p.aliasPrefix | ||||||
|  |           ? `https://solsynth.dev/posts/${p.aliasPrefix}/${p.alias}` | ||||||
|  |           : `https://solsynth.dev/posts/${p.id}`, | ||||||
|  |       content: p.body.content, | ||||||
|  |       date: new Date(p.publishedAt ?? p.createdAt), | ||||||
|  |       published: new Date(p.publishedAt ?? p.createdAt), | ||||||
|  |       copyright: `All right reserved ${new Date().getFullYear()} © @${p.publisher.name}`, | ||||||
|  |       author: [ | ||||||
|  |         { | ||||||
|  |           name: `@${p.publisher.name}`, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return feed.rss2() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export const getServerSideProps: GetServerSideProps = async ({ res, query }) => { | ||||||
|  |   let page: number = parseInt(query.page as string) | ||||||
|  |   if (isNaN(page)) page = 1 | ||||||
|  |  | ||||||
|  |   const countPerPage = 20 | ||||||
|  |  | ||||||
|  |   const { data: resp } = await sni.get<{ data: SnPost[] }>('/cgi/co/posts', { | ||||||
|  |     params: { | ||||||
|  |       take: countPerPage, | ||||||
|  |       offset: (page - 1) * countPerPage, | ||||||
|  |     }, | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   const sitemap = generateFeed(resp.data) | ||||||
|  |  | ||||||
|  |   res.setHeader('Content-Type', 'text/xml') | ||||||
|  |   res.write(sitemap) | ||||||
|  |   res.end() | ||||||
|  |  | ||||||
|  |   return { | ||||||
|  |     props: {}, | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default function PostSiteMap() { | ||||||
|  |   // getServerSideProps will do the heavy lifting | ||||||
|  | } | ||||||
| @@ -2,9 +2,20 @@ import { AttachmentItem } from '@/components/attachments/AttachmentItem' | |||||||
| import { SnAttachment, listAttachment } from '@/services/attachment' | import { SnAttachment, listAttachment } from '@/services/attachment' | ||||||
| import { getAttachmentUrl, sni } from '@/services/network' | import { getAttachmentUrl, sni } from '@/services/network' | ||||||
| import { SnPost } from '@/services/post' | import { SnPost } from '@/services/post' | ||||||
| import { Avatar, Box, Container, Divider, Grid2 as Grid, Pagination, Paper, Typography } from '@mui/material' | import { | ||||||
|  |   Avatar, | ||||||
|  |   Box, | ||||||
|  |   Button, | ||||||
|  |   Container, | ||||||
|  |   Divider, | ||||||
|  |   Grid2 as Grid, | ||||||
|  |   Link, | ||||||
|  |   Pagination, | ||||||
|  |   Paper, | ||||||
|  |   Typography, | ||||||
|  | } from '@mui/material' | ||||||
| import { GetServerSideProps, InferGetServerSidePropsType } from 'next' | import { GetServerSideProps, InferGetServerSidePropsType } from 'next' | ||||||
| import Link from 'next/link' | import NextLink from 'next/link' | ||||||
| import { useRouter } from 'next/router' | import { useRouter } from 'next/router' | ||||||
| import rehypeSanitize from 'rehype-sanitize' | import rehypeSanitize from 'rehype-sanitize' | ||||||
| import rehypeStringify from 'rehype-stringify' | import rehypeStringify from 'rehype-stringify' | ||||||
| @@ -86,7 +97,7 @@ export default function PostList({ posts, page, pages }: InferGetServerSideProps | |||||||
|             </Box> |             </Box> | ||||||
|           </Box> |           </Box> | ||||||
|  |  | ||||||
|           <Link href={`/posts/${p.id}`} passHref> |           <NextLink href={`/posts/${p.id}`} passHref> | ||||||
|             <Box> |             <Box> | ||||||
|               <Box sx={{ mt: 1.5, mb: 1 }} display="flex" flexDirection="column" gap={0.5}> |               <Box sx={{ mt: 1.5, mb: 1 }} display="flex" flexDirection="column" gap={0.5}> | ||||||
|                 {(p.body.title || p.body.content) && ( |                 {(p.body.title || p.body.content) && ( | ||||||
| @@ -108,7 +119,7 @@ export default function PostList({ posts, page, pages }: InferGetServerSideProps | |||||||
|                 {p.body.content && <div dangerouslySetInnerHTML={{ __html: p.body.content }} />} |                 {p.body.content && <div dangerouslySetInnerHTML={{ __html: p.body.content }} />} | ||||||
|               </Box> |               </Box> | ||||||
|             </Box> |             </Box> | ||||||
|           </Link> |           </NextLink> | ||||||
|  |  | ||||||
|           {p.attachments && ( |           {p.attachments && ( | ||||||
|             <Grid |             <Grid | ||||||
| @@ -131,12 +142,24 @@ export default function PostList({ posts, page, pages }: InferGetServerSideProps | |||||||
|         </Paper> |         </Paper> | ||||||
|       ))} |       ))} | ||||||
|  |  | ||||||
|       <Pagination |       <Box | ||||||
|         count={pages} |         sx={{ | ||||||
|         page={page} |           mx: 'auto', | ||||||
|         sx={{ mx: 'auto', mb: 5, mt: 3 }} |           mb: 5, | ||||||
|         onChange={(_, page) => router.push('/posts?page=' + page)} |           mt: 3, | ||||||
|       /> |           display: 'flex', | ||||||
|  |           flexDirection: 'column', | ||||||
|  |           justifyContent: 'center', | ||||||
|  |           placeItems: 'center', | ||||||
|  |           gap: 1.5, | ||||||
|  |           textAlign: 'center', | ||||||
|  |         }} | ||||||
|  |       > | ||||||
|  |         <Pagination count={pages} page={page} onChange={(_, page) => router.push('/posts?page=' + page)} /> | ||||||
|  |         <NextLink passHref href="/posts/feed" target="_blank" prefetch={false}> | ||||||
|  |           <Link fontSize={13}>RSS Feed</Link> | ||||||
|  |         </NextLink> | ||||||
|  |       </Box> | ||||||
|     </Container> |     </Container> | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user