From 6122e4ac5fa137be031ea7f8e71993bac81e2c1a Mon Sep 17 00:00:00 2001 From: Alex Patterson Date: Thu, 18 May 2023 22:14:25 -0400 Subject: [PATCH 1/6] fix start date (#453) --- .../3-12-how-to-become-a-full-time-content-creator/+page.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/codingcatdev/src/routes/(content-single)/(non-course)/podcast/3-12-how-to-become-a-full-time-content-creator/+page.md b/apps/codingcatdev/src/routes/(content-single)/(non-course)/podcast/3-12-how-to-become-a-full-time-content-creator/+page.md index 0e2e63cb..89d41ec4 100644 --- a/apps/codingcatdev/src/routes/(content-single)/(non-course)/podcast/3-12-how-to-become-a-full-time-content-creator/+page.md +++ b/apps/codingcatdev/src/routes/(content-single)/(non-course)/podcast/3-12-how-to-become-a-full-time-content-creator/+page.md @@ -49,7 +49,7 @@ slug: 3-12-how-to-become-a-full-time-content-creator sponsors: - storyblok spotify: https://open.spotify.com/episode/1alJwQzJmCoWupGZIvaRQd?si=8bbf17162e77479e -start: May 10, 2023 +start: May 18, 2023 title: 'How to Become a Full Time Content Creator' youtube: https://youtu.be/WPrmIwDBodM --- From c66f93ae149b68c9587e0c5ebd095206e6f48d26 Mon Sep 17 00:00:00 2001 From: Alex Patterson Date: Fri, 19 May 2023 10:50:05 -0400 Subject: [PATCH 2/6] add author and html to rss (#455) --- apps/codingcatdev/src/lib/server/content.ts | 36 +++++++++---------- .../routes/(rss)/feed-blog.json/+server.ts | 31 ++++++++++++++++ .../src/routes/(rss)/feed-blog.xml/+server.ts | 11 ++++-- .../routes/(rss)/feed-courses.json/+server.ts | 31 ++++++++++++++++ .../routes/(rss)/feed-courses.xml/+server.ts | 14 +++++--- .../(rss)/feed-podcasts.json/+server.ts | 31 ++++++++++++++++ .../routes/(rss)/feed-podcasts.xml/+server.ts | 13 ++++--- apps/codingcatdev/src/routes/(rss)/rss.ts | 25 ++++++++----- 8 files changed, 152 insertions(+), 40 deletions(-) create mode 100644 apps/codingcatdev/src/routes/(rss)/feed-blog.json/+server.ts create mode 100644 apps/codingcatdev/src/routes/(rss)/feed-courses.json/+server.ts create mode 100644 apps/codingcatdev/src/routes/(rss)/feed-podcasts.json/+server.ts diff --git a/apps/codingcatdev/src/lib/server/content.ts b/apps/codingcatdev/src/lib/server/content.ts index 6733af1e..56420a9c 100644 --- a/apps/codingcatdev/src/lib/server/content.ts +++ b/apps/codingcatdev/src/lib/server/content.ts @@ -3,6 +3,7 @@ import type { Content, Course } from '$lib/types'; import { env } from '$env/dynamic/private'; import { fileURLToPath } from 'url'; import { opendirSync } from "fs"; +import type { SvelteComponentTyped, ComponentType } from 'svelte'; const LIMIT = 20; @@ -29,45 +30,33 @@ export const getRootPath = (contentType: ContentType, courseDir?: string) => { return root; } -export const getContentTypeDirectory = async (contentType: ContentType, courseDir?: string) => { +export const getContentTypeDirectory = async (contentType: ContentType, courseDir?: string, render = false) => { const contentList: T[] = []; const dirs = opendirSync(getRootPath(contentType, courseDir)); for await (const dir of dirs) { if (dir.isFile()) continue; - const parsed = await parseContentType(`${getRootPath(contentType, courseDir)}/${dir.name}/${suffix}`) as T; + const parsed = await parseContentType(`${getRootPath(contentType, courseDir)}/${dir.name}/${suffix}`, render) as T; contentList.push(parsed); } return contentList; } -export const getContentTypePath = async (contentType: ContentType, path: string, courseDir?: string) => { +export const getContentTypePath = async (contentType: ContentType, path: string, courseDir?: string, render = false) => { const root = getRootPath(contentType, courseDir); - return await parseContentType(`${root}/${path}/${suffix}`) as T; + return await parseContentType(`${root}/${path}/${suffix}`, render) as T; } -export const parseContentType = (async (path: string) => { - // If we are in production everything has already been compiled to JavaScript - // and we can just read the metadata, otherwise compile and read. - // let frontmatter; - // let html; - // if (prod) { - const { metadata } = await import(path); +export const parseContentType = (async (path: string, render = false) => { + const { metadata, default: page } = await import(path); const frontmatter = metadata; - // } else { - // console.log('READ PATH', path); - // const md = readFileSync(path, 'utf8'); - // const transformed = await compile(md); - // html = transformed?.code; - // frontmatter = transformed?.data?.fm as Content & Podcast | undefined; - // } - // TODO: Add more checks? + // TODO: Add more checks? if (!frontmatter?.type) { console.error('Missing Frontmatter details', path); return; } - const content = { + let content = { ...frontmatter, cover: frontmatter?.cover ? decodeURI(frontmatter?.cover) : '', type: frontmatter?.type as ContentType, @@ -77,6 +66,13 @@ export const parseContentType = (async (path: string) => { slug: path.split('/').at(-2) }; + if (render) { + content = { + ...content, + html: page?.render()?.html + } + } + if (frontmatter.type === ContentType.course) { const lesson = (await listContent({ contentItems: await getContentTypeDirectory(ContentType.lesson, frontmatter.slug), diff --git a/apps/codingcatdev/src/routes/(rss)/feed-blog.json/+server.ts b/apps/codingcatdev/src/routes/(rss)/feed-blog.json/+server.ts new file mode 100644 index 00000000..57fb7484 --- /dev/null +++ b/apps/codingcatdev/src/routes/(rss)/feed-blog.json/+server.ts @@ -0,0 +1,31 @@ +import { getContentTypeDirectory, listContent } from '$lib/server/content'; +import { ContentType, type Content, type Author } from '$lib/types'; +import { buildFeed } from '../rss'; + + +const contentType = ContentType.post; + +/** @type {import('./$types').RequestHandler} */ +export const GET = async () => { + const contentItems = (await listContent({ + contentItems: await getContentTypeDirectory(contentType, undefined, true), + limit: 10000 + })).content + + const authorItems = (await listContent({ + contentItems: await getContentTypeDirectory(ContentType.author), + limit: 10000 + })).content + + //xml rss feed response + return new Response( + buildFeed({ + contentType, contents: contentItems, authorItems + }).json1(), + { + headers: { + 'content-type': 'application/json', 'cache-control': 'max-age=0, s-maxage=3600', + }, + } + ) +} diff --git a/apps/codingcatdev/src/routes/(rss)/feed-blog.xml/+server.ts b/apps/codingcatdev/src/routes/(rss)/feed-blog.xml/+server.ts index af1d581d..9f838ccb 100644 --- a/apps/codingcatdev/src/routes/(rss)/feed-blog.xml/+server.ts +++ b/apps/codingcatdev/src/routes/(rss)/feed-blog.xml/+server.ts @@ -1,5 +1,5 @@ import { getContentTypeDirectory, listContent } from '$lib/server/content'; -import { ContentType, type Content } from '$lib/types'; +import { ContentType, type Content, type Author } from '$lib/types'; import { buildFeed } from '../rss'; @@ -8,14 +8,19 @@ const contentType = ContentType.post; /** @type {import('./$types').RequestHandler} */ export const GET = async () => { const contentItems = (await listContent({ - contentItems: await getContentTypeDirectory(ContentType.post), + contentItems: await getContentTypeDirectory(contentType, undefined, true), + limit: 10000 + })).content + + const authorItems = (await listContent({ + contentItems: await getContentTypeDirectory(ContentType.author), limit: 10000 })).content //xml rss feed response return new Response( buildFeed({ - contentType, contents: (await listContent({ contentItems, limit: 10000 })).content + contentType, contents: contentItems, authorItems }).rss2(), { headers: { diff --git a/apps/codingcatdev/src/routes/(rss)/feed-courses.json/+server.ts b/apps/codingcatdev/src/routes/(rss)/feed-courses.json/+server.ts new file mode 100644 index 00000000..ff714489 --- /dev/null +++ b/apps/codingcatdev/src/routes/(rss)/feed-courses.json/+server.ts @@ -0,0 +1,31 @@ +import { getContentTypeDirectory, listContent } from '$lib/server/content'; +import { ContentType, type Content, type Author } from '$lib/types'; +import { buildFeed } from '../rss'; + + +const contentType = ContentType.course; + +/** @type {import('./$types').RequestHandler} */ +export const GET = async () => { + const contentItems = (await listContent({ + contentItems: await getContentTypeDirectory(contentType, undefined, true), + limit: 10000 + })).content + + const authorItems = (await listContent({ + contentItems: await getContentTypeDirectory(ContentType.author), + limit: 10000 + })).content + + //xml rss feed response + return new Response( + buildFeed({ + contentType, contents: contentItems, authorItems + }).json1(), + { + headers: { + 'content-type': 'application/json', 'cache-control': 'max-age=0, s-maxage=3600', + }, + } + ) +} diff --git a/apps/codingcatdev/src/routes/(rss)/feed-courses.xml/+server.ts b/apps/codingcatdev/src/routes/(rss)/feed-courses.xml/+server.ts index 7ca7a69e..87ac5cf6 100644 --- a/apps/codingcatdev/src/routes/(rss)/feed-courses.xml/+server.ts +++ b/apps/codingcatdev/src/routes/(rss)/feed-courses.xml/+server.ts @@ -1,20 +1,26 @@ import { getContentTypeDirectory, listContent } from '$lib/server/content'; -import { ContentType, type Content } from '$lib/types'; +import { ContentType, type Content, type Author } from '$lib/types'; import { buildFeed } from '../rss'; -const contentType = ContentType.post; +const contentType = ContentType.course; /** @type {import('./$types').RequestHandler} */ export const GET = async () => { const contentItems = (await listContent({ - contentItems: await getContentTypeDirectory(ContentType.course), + contentItems: await getContentTypeDirectory(contentType, undefined, true), limit: 10000 })).content + + const authorItems = (await listContent({ + contentItems: await getContentTypeDirectory(ContentType.author), + limit: 10000 + })).content + //xml rss feed response return new Response( buildFeed({ - contentType, contents: (await listContent({ contentItems, limit: 10000 })).content + contentType, contents: contentItems, authorItems }).rss2(), { headers: { diff --git a/apps/codingcatdev/src/routes/(rss)/feed-podcasts.json/+server.ts b/apps/codingcatdev/src/routes/(rss)/feed-podcasts.json/+server.ts new file mode 100644 index 00000000..744eeb74 --- /dev/null +++ b/apps/codingcatdev/src/routes/(rss)/feed-podcasts.json/+server.ts @@ -0,0 +1,31 @@ +import { getContentTypeDirectory, listContent } from '$lib/server/content'; +import { ContentType, type Content, type Author } from '$lib/types'; +import { buildFeed } from '../rss'; + + +const contentType = ContentType.podcast; + +/** @type {import('./$types').RequestHandler} */ +export const GET = async () => { + const contentItems = (await listContent({ + contentItems: await getContentTypeDirectory(contentType, undefined, true), + limit: 10000 + })).content + + const authorItems = (await listContent({ + contentItems: await getContentTypeDirectory(ContentType.author), + limit: 10000 + })).content + + //xml rss feed response + return new Response( + buildFeed({ + contentType, contents: contentItems, authorItems + }).json1(), + { + headers: { + 'content-type': 'application/json', 'cache-control': 'max-age=0, s-maxage=3600', + }, + } + ) +} diff --git a/apps/codingcatdev/src/routes/(rss)/feed-podcasts.xml/+server.ts b/apps/codingcatdev/src/routes/(rss)/feed-podcasts.xml/+server.ts index 6fa5691e..2647f0fa 100644 --- a/apps/codingcatdev/src/routes/(rss)/feed-podcasts.xml/+server.ts +++ b/apps/codingcatdev/src/routes/(rss)/feed-podcasts.xml/+server.ts @@ -1,21 +1,26 @@ import { getContentTypeDirectory, listContent } from '$lib/server/content'; -import { ContentType, type Content } from '$lib/types'; +import { ContentType, type Content, type Author } from '$lib/types'; import { buildFeed } from '../rss'; -const contentType = ContentType.post; +const contentType = ContentType.podcast; /** @type {import('./$types').RequestHandler} */ export const GET = async () => { const contentItems = (await listContent({ - contentItems: await getContentTypeDirectory(ContentType.podcast), + contentItems: await getContentTypeDirectory(contentType, undefined, true), + limit: 10000 + })).content + + const authorItems = (await listContent({ + contentItems: await getContentTypeDirectory(ContentType.author), limit: 10000 })).content //xml rss feed response return new Response( buildFeed({ - contentType, contents: (await listContent({ contentItems, limit: 10000 })).content + contentType, contents: contentItems, authorItems }).rss2(), { headers: { diff --git a/apps/codingcatdev/src/routes/(rss)/rss.ts b/apps/codingcatdev/src/routes/(rss)/rss.ts index bc3a234b..30dd37a6 100644 --- a/apps/codingcatdev/src/routes/(rss)/rss.ts +++ b/apps/codingcatdev/src/routes/(rss)/rss.ts @@ -1,5 +1,5 @@ -import type { Content, ContentType } from "$lib/types"; -import { Feed } from 'feed'; +import type { Content, ContentType, Author } from "$lib/types"; +import { Feed, type Author as FeedAuthor } from 'feed'; const site = 'https://codingcat.dev'; @@ -7,9 +7,11 @@ const site = 'https://codingcat.dev'; export const buildFeed = ({ contents, contentType, + authorItems, }: { contents: Content[]; contentType: ContentType; + authorItems: Author[]; }) => { const feed = new Feed({ title: `${site} - ${contentType} feed`, @@ -32,22 +34,27 @@ export const buildFeed = ({ }); for (const content of contents) { + + const authors = content?.authors?.map((authorSlug) => { + return authorItems.filter(a => a.slug === authorSlug).map(a => { + return { + name: a.name, + link: `${site}/author/${a.slug}`, + } + })?.at(0) as FeedAuthor; + }).filter(a => a !== undefined) feed.addItem({ title: content.title || '', + content: content.html, link: `${site}/${contentType}/${content.slug}`, description: `${content.excerpt}`, image: content.cover || feed.items.at(0)?.image, date: content.start || new Date(), - author: content?.authors ? content.authors?.map((author) => { - return { - name: author.name, - link: `${site}/authors/${author.slug}`, - }; - }) + author: authors ? authors : [{ name: 'Alex Patterson', email: 'alex@codingcat.dev', - link: `${site}`, + link: `${site}/author/alex-patterson`, }], }); } From bce8ebc1d7ae15ecbae49280a55fd2502a3fc010 Mon Sep 17 00:00:00 2001 From: Alex Patterson Date: Fri, 19 May 2023 13:23:40 -0400 Subject: [PATCH 3/6] Fix/course data (#457) * Fixed Author lookup * update sponsor listings --- .../(content-single)/course/+layout.server.ts | 26 ++++++++++---- .../(content-single)/course/+layout.svelte | 33 +++++++++++++++++ .../(content-single)/course/Course.svelte | 35 ++++++++++++++++++- 3 files changed, 87 insertions(+), 7 deletions(-) diff --git a/apps/codingcatdev/src/routes/(content-single)/course/+layout.server.ts b/apps/codingcatdev/src/routes/(content-single)/course/+layout.server.ts index 5574497f..9a36e9c5 100644 --- a/apps/codingcatdev/src/routes/(content-single)/course/+layout.server.ts +++ b/apps/codingcatdev/src/routes/(content-single)/course/+layout.server.ts @@ -1,6 +1,6 @@ import { error } from '@sveltejs/kit'; -import { filterContent, getContentTypeDirectory, getContentTypePath, listContent } from '$lib/server/content'; -import { ContentType, type Course, type Author } from '$lib/types'; +import { filterContent, getContentTypePath } from '$lib/server/content'; +import { ContentType, type Course, type Author, type Sponsor } from '$lib/types'; export const prerender = false; @@ -18,14 +18,28 @@ export const load = (async (params) => { const course = contentItems?.at(0); if (!course) throw error(404); - const authors = (await listContent({ - contentItems: await getContentTypeDirectory(ContentType.author) - }))?.content + // Content is good, fetch surrounding items + const authors: Author[] = []; + if (course?.authors) { + for (const authorSlug of course.authors) { + const author = await getContentTypePath(ContentType.author, authorSlug); + if (author) authors.push(author) + } + } + + const sponsors: Sponsor[] = []; + if (course?.sponsors) { + for (const sponsorSlug of course.sponsors) { + const sponsor = await getContentTypePath(ContentType.sponsor, sponsorSlug); + if (sponsor) sponsors.push(sponsor) + } + } return { content: course.lesson?.find(l => l.slug === lessonSlug), course, - authors + authors, + sponsors } } catch (e) { diff --git a/apps/codingcatdev/src/routes/(content-single)/course/+layout.svelte b/apps/codingcatdev/src/routes/(content-single)/course/+layout.svelte index 765fd501..1167e4a9 100644 --- a/apps/codingcatdev/src/routes/(content-single)/course/+layout.svelte +++ b/apps/codingcatdev/src/routes/(content-single)/course/+layout.svelte @@ -3,6 +3,7 @@ import Course from './Course.svelte'; import LessonList from './LessonList.svelte'; import Lesson from './Lesson.svelte'; + import Image from '$lib/components/content/Image.svelte'; import { storeCurrentUrl } from '$lib/stores/stores'; export let data; @@ -24,6 +25,38 @@
+ + {#if data?.sponsors?.length} + Sponsors +
+ {#each data?.sponsors as sponsor (sponsor.slug)} + +
+ {#if sponsor?.cover} + {sponsor.name} + {/if} +
+
+

{sponsor?.name}

+
+

+ {sponsor?.description} +

+
+
+
+ {/each} +
+ {/if} {#key $storeCurrentUrl} {/key} diff --git a/apps/codingcatdev/src/routes/(content-single)/course/Course.svelte b/apps/codingcatdev/src/routes/(content-single)/course/Course.svelte index 025fb2b8..3ba78901 100644 --- a/apps/codingcatdev/src/routes/(content-single)/course/Course.svelte +++ b/apps/codingcatdev/src/routes/(content-single)/course/Course.svelte @@ -1,12 +1,13 @@ @@ -44,6 +45,38 @@ {/if}

{data.course.title}

+ + {#if data?.sponsors?.length} +

Sponsors

+
+ {#each data?.sponsors as sponsor (sponsor.slug)} + +
+ {#if sponsor?.cover} + {sponsor.name} + {/if} +
+
+

{sponsor?.name}

+
+

+ {sponsor?.description} +

+
+
+
+ {/each} +
+ {/if}
From 49c19f575c1c91fb42a8ae42ff5fcc692d7bb22d Mon Sep 17 00:00:00 2001 From: Alex Patterson Date: Fri, 19 May 2023 14:21:03 -0400 Subject: [PATCH 4/6] add key to force refresh of data (#460) --- .../(content-single)/course/Lesson.svelte | 158 +++++++++--------- 1 file changed, 80 insertions(+), 78 deletions(-) diff --git a/apps/codingcatdev/src/routes/(content-single)/course/Lesson.svelte b/apps/codingcatdev/src/routes/(content-single)/course/Lesson.svelte index 5ac8c652..5c22c72a 100644 --- a/apps/codingcatdev/src/routes/(content-single)/course/Lesson.svelte +++ b/apps/codingcatdev/src/routes/(content-single)/course/Lesson.svelte @@ -13,86 +13,88 @@ {#if data?.content} -
-
-
-
    -
  1. Courses
  2. -
  3. -
  4. - {data.course.title} -
  5. -
  6. -
  7. {data.content.title}
  8. -
- {#if data?.content?.youtube} -