Skip to content

Commit

Permalink
rss and json feeds work
Browse files Browse the repository at this point in the history
  • Loading branch information
nemanjam committed Jun 19, 2024
1 parent bd52b61 commit 7a83ac5
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 75 deletions.
4 changes: 2 additions & 2 deletions docs/todo3.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ link and primary single color
put all components and variants in styleguide
daisy ui color-mix() and oklch just to calc hover colors, i dont need it, hardcode it
sorted archive like in astro-cactus, route param highlight and link
refactor rss and json feed
refactor rss and json feed
ProjectCard and test markdown
PostCardSmall
extract types from constants
Expand All @@ -327,5 +327,5 @@ layout bottom padding
start writing readme
working-notes folder in docs
footer commit toast

draft preview for prod
```
17 changes: 16 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
// all relative imports in config subtree
import dotenv from 'dotenv';

import { configSchema } from './schemas/config';
import { configSchema, nodeEnvValues } from './schemas/config';
import { validateConfig } from './utils/config';

import type { ConfigType } from './types/config';

/*------------------ load .env file -----------------*/

const NODE_ENV = process.env.NODE_ENV;

if (!nodeEnvValues.includes(NODE_ENV)) {
console.error('Invalid process.env.NODE_ENV: ', NODE_ENV);
throw new Error('Invalid process.env.NODE_ENV');
}

const envFileName = `.env.${NODE_ENV}`;
dotenv.config({ path: envFileName });

/*-------------------- configData -------------------*/

const configData: ConfigType = {
NODE_ENV: process.env.NODE_ENV,
/** without '/' */
Expand Down
2 changes: 2 additions & 0 deletions src/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@ export const ROUTES = {
},
API: {
OG_IMAGES: '/api/open-graph/',
FEED_JSON: '/api/feed.json',
FEED_RSS: '/api/feed.xml',
},
} as const;
10 changes: 7 additions & 3 deletions src/modules/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,22 @@ import type { CollectionEntry, CollectionKey } from 'astro:content';

export interface GetAllEntriesOptions {
skipSort?: boolean;
includeDrafts?: boolean;
}

/** Sorts by publishDate desc by default. Newest on top. */
/**
* Sorts by publishDate desc by default. Newest on top.
* Omits drafts by default.
*/
export const getAllEntries = async <T extends CollectionKey>(
collectionName: T,
options?: GetAllEntriesOptions
): Promise<CollectionEntry<T>[]> => {
const { skipSort = false } = options ?? {};
const { skipSort = false, includeDrafts = false } = options ?? {};

const entries = await getCollection<T>(collectionName, ({ data }) => {
const isProdAndDraft = import.meta.env.PROD && data.draft;
return !isProdAndDraft;
return !isProdAndDraft || includeDrafts;
});

if (skipSort) return entries;
Expand Down
106 changes: 53 additions & 53 deletions src/modules/feed.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,70 @@
import { createMarkdownProcessor } from '@astrojs/markdown-remark';

import { Feed } from 'feed';

import { getAllPosts } from '@/modules/post/common';
import { ROUTES } from '@/constants/routes';
import { CONFIG } from '@/config';
import { renderMarkdown } from '@/utils/markdown';

import type { Item } from 'feed';

const { SITE_DESCRIPTION, SITE_TITLE, SITE_URL } = CONFIG;
const { SITE_DESCRIPTION, SITE_TITLE, SITE_URL, AUTHOR_NAME, AUTHOR_EMAIL } = CONFIG;

const author = {
name: 'Nemanja Mitic',
email: '[email protected]',
link: `${SITE_URL}/about`,
};
const copyright = (date: Date) => `&copy;${date.getFullYear()} copyright text`;
export const getFeed = async (): Promise<Feed> => {
const author = {
name: AUTHOR_NAME,
email: AUTHOR_EMAIL,
link: `${SITE_URL}${ROUTES.ABOUT}`,
};

export const feed = new Feed({
title: SITE_TITLE,
description: SITE_DESCRIPTION,
id: SITE_URL,
link: SITE_URL,
language: 'en',
image: `${SITE_URL}/images/favicons/favicon-32x32.png`,
favicon: `${SITE_URL}/favicon.ico`,
copyright: copyright(new Date()),
updated: new Date(),
feedLinks: {
json: `${SITE_URL}/feed.json`,
rss: `${SITE_URL}/feed.xml`,
},
author,
});
const copyright = (date: Date) =>
`&copy;${date.getFullYear()} ${AUTHOR_NAME}. All rights reserved.`;

const sortedRawPosts = await getAllPosts();
const feed = new Feed({
title: SITE_TITLE,
description: SITE_DESCRIPTION,
id: SITE_URL,
link: SITE_URL,
language: 'en',
image: `${SITE_URL}/images/favicons/favicon-32x32.png`,
favicon: `${SITE_URL}/favicon.ico`,
copyright: copyright(new Date()),
updated: new Date(),
feedLinks: {
json: `${SITE_URL}${ROUTES.API.FEED_JSON}`,
rss: `${SITE_URL}${ROUTES.API.FEED_RSS}`,
},
author,
});

const { render: renderMarkdown } = await createMarkdownProcessor({});
const sortedPosts = await getAllPosts();

for (const post of sortedRawPosts) {
const match = post.slug.match(/^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})-(?<slug>.+)/);
if (!match || !post.slug || post.data.draft) {
continue;
}
const slug = Object.values(match.groups!).join('/');
for (const post of sortedPosts) {
const { data, body, slug } = post;
const { title, description, publishDate, heroImage, noHero, draft } = data;

const url = `${SITE_URL}${ROUTES.BLOG}${slug}/`;
const { code: description } = await renderMarkdown(
`${post.data.description || ''}\n\n[Continue reading…](${url})`
);
const { code: content } = await renderMarkdown(post.body);
// omit drafts
if (draft) continue;

const item: Item = {
title: post.data.title,
description,
id: url,
link: url,
date: post.data.publishDate,
published: post.data.publishDate,
author: [author],
copyright: copyright(post.data.publishDate),
content,
};
if (post.data.heroImage?.src) {
item.image = `${SITE_URL}${post.data.heroImage.src}`;
const url = `${SITE_URL}${ROUTES.BLOG}${slug}/`;
const { code: content } = await renderMarkdown(body);

const item: Item = {
title,
description,
id: url,
link: url,
date: publishDate,
published: publishDate,
author: [author],
copyright: copyright(publishDate),
content,
...(noHero ? { image: `${SITE_URL}${heroImage.src}` } : {}),
};

feed.addItem(item);
}

feed.addItem(item);
}
return feed;
};

export const feed = await getFeed();
File renamed without changes.
File renamed without changes.
6 changes: 5 additions & 1 deletion src/schemas/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ export const nodeEnvValues = ['development', 'test', 'production'] as const;

export const configSchema = z.object({
NODE_ENV: z.enum(nodeEnvValues),
SITE_URL: z.string().url(),
// ensure no trailing slash
SITE_URL: z
.string()
.url()
.regex(/[^\/]$/, 'SITE_URL should not end with a slash'),
SITE_TITLE: z.string().min(1),
SITE_DESCRIPTION: z.string().min(1),
PAGE_SIZE: z.object({
Expand Down
15 changes: 0 additions & 15 deletions src/utils/config.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,8 @@
import dotenv from 'dotenv';
// @ts-expect-error, js lib
import treeify from 'object-treeify';

import { nodeEnvValues } from '../schemas/config';

import type { ConfigSchemaType, ConfigType } from '../types/config';

/*------------------ load .env file -----------------*/

const NODE_ENV = process.env.NODE_ENV;

if (!nodeEnvValues.includes(NODE_ENV)) {
console.error('Invalid process.env.NODE_ENV: ', NODE_ENV);
throw new Error('Invalid process.env.NODE_ENV');
}

const envFileName = `.env.${NODE_ENV}`;
dotenv.config({ path: envFileName });

/*------------------ validation -----------------*/

export const validateConfig = (
Expand Down

0 comments on commit 7a83ac5

Please sign in to comment.