fix: migrate videos

feat/collection
Arik Chakma 2 months ago
parent 2333694e2c
commit 33b617ef85
  1. 2
      src/components/VideoHeader.astro
  2. 12
      src/components/VideoListItem.astro
  3. 2
      src/content/config.ts
  4. 24
      src/content/video.ts
  5. 0
      src/content/videos/acid-explained.md
  6. 0
      src/content/videos/all-about-http-caching.md
  7. 0
      src/content/videos/array-structure.md
  8. 0
      src/content/videos/arrays-and-objects-in-javascript.md
  9. 0
      src/content/videos/async-javascript.md
  10. 0
      src/content/videos/basic-authentication.md
  11. 0
      src/content/videos/basics-of-authentication.md
  12. 0
      src/content/videos/big-o-notation.md
  13. 0
      src/content/videos/content-delivery-networks.md
  14. 0
      src/content/videos/dns-explained.md
  15. 0
      src/content/videos/dns-records.md
  16. 0
      src/content/videos/floating-point-arithmetic.md
  17. 0
      src/content/videos/freeze-and-seal-objects-in-javascript.md
  18. 0
      src/content/videos/graph-data-structure.md
  19. 0
      src/content/videos/hash-table-data-structure.md
  20. 0
      src/content/videos/heap-data-structure.md
  21. 0
      src/content/videos/how-to-use-css-variables.md
  22. 0
      src/content/videos/how-to-use-github-actions.md
  23. 0
      src/content/videos/javascript-fetch-api.md
  24. 0
      src/content/videos/linked-list-data-structure.md
  25. 0
      src/content/videos/load-balancers-101.md
  26. 0
      src/content/videos/osi-model.md
  27. 0
      src/content/videos/practical-intro-to-react.md
  28. 0
      src/content/videos/promises-in-javascript.md
  29. 0
      src/content/videos/queue-data-structure.md
  30. 0
      src/content/videos/random-number-generators.md
  31. 0
      src/content/videos/scaling-the-unscalable.md
  32. 0
      src/content/videos/session-based-authentication.md
  33. 0
      src/content/videos/ssh-ssl-tls.md
  34. 0
      src/content/videos/stack-data-structure.md
  35. 0
      src/content/videos/system-design-101.md
  36. 0
      src/content/videos/tcp-ip-model.md
  37. 0
      src/content/videos/transport-protocols-tcp-vs-udp.md
  38. 0
      src/content/videos/tree-data-structure.md
  39. 0
      src/content/videos/what-are-data-structures.md
  40. 0
      src/content/videos/what-is-cap-theorem.md
  41. 0
      src/content/videos/what-is-dependency-injection.md
  42. 0
      src/content/videos/what-is-dom-shadow-dom-virtual-dom.md
  43. 0
      src/content/videos/what-is-eventual-consistency.md
  44. 0
      src/content/videos/yaml-in-depth.md
  45. 81
      src/lib/video.ts
  46. 6
      src/pages/pages.json.ts
  47. 11
      src/pages/videos/[videoId].astro

@ -6,7 +6,7 @@ export interface Props {
} }
const { video } = Astro.props; const { video } = Astro.props;
const { frontmatter, author } = video; const { data: frontmatter, author } = video;
--- ---
<div class='border-b bg-white py-5 sm:py-12'> <div class='border-b bg-white py-5 sm:py-12'>

@ -6,21 +6,21 @@ export interface Props {
} }
const { video } = Astro.props; const { video } = Astro.props;
const { frontmatter, id } = video; const { data: frontmatter, slug: id } = video;
--- ---
<a <a
class:list={[ class:list={[
'block no-underline py-2 group text-md items-center text-gray-600 hover:text-blue-600 flex justify-between border-b', 'text-md group block flex items-center justify-between border-b py-2 text-gray-600 no-underline hover:text-blue-600',
]} ]}
href={`/videos/${id}`} href={`/videos/${id}`}
> >
<span class='group-hover:translate-x-2 transition-transform'> <span class='transition-transform group-hover:translate-x-2'>
{frontmatter.title} {frontmatter.title}
{ {
frontmatter.isNew && ( frontmatter.isNew && (
<span class='bg-green-300 text-green-900 text-xs font-medium px-1.5 py-0.5 rounded-sm uppercase ml-1.5'> <span class='ml-1.5 rounded-sm bg-green-300 px-1.5 py-0.5 text-xs font-medium uppercase text-green-900'>
New New
<span class='hidden sm:inline'> <span class='hidden sm:inline'>
&middot; &middot;
@ -32,9 +32,9 @@ const { frontmatter, id } = video;
) )
} }
</span> </span>
<span class='capitalize text-gray-500 text-xs hidden sm:block'> <span class='hidden text-xs capitalize text-gray-500 sm:block'>
{frontmatter.duration} {frontmatter.duration}
</span> </span>
<span class='text-gray-400 text-xs block sm:hidden'> &raquo;</span> <span class='block text-xs text-gray-400 sm:hidden'> &raquo;</span>
</a> </a>

@ -2,10 +2,12 @@ import { authorCollection } from './author';
import { guideCollection } from './guide'; import { guideCollection } from './guide';
import { projectCollection } from './project'; import { projectCollection } from './project';
import { questionGroupCollection } from './question-group'; import { questionGroupCollection } from './question-group';
import { videoCollection } from './video';
export const collections = { export const collections = {
authors: authorCollection, authors: authorCollection,
guides: guideCollection, guides: guideCollection,
'question-groups': questionGroupCollection, 'question-groups': questionGroupCollection,
projects: projectCollection, projects: projectCollection,
videos: videoCollection,
}; };

@ -0,0 +1,24 @@
import { defineCollection, z } from 'astro:content';
export const videoCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
authorId: z.string(),
seo: z
.object({
title: z.string().optional(),
description: z.string().optional(),
})
.optional(),
isNew: z.boolean(),
duration: z.string(),
date: z.date(),
sitemap: z.object({
priority: z.number(),
changefreq: z.enum(['daily', 'weekly', 'monthly', 'yearly']),
}),
tags: z.array(z.string()),
}),
});

@ -1,44 +1,12 @@
import type { MarkdownFileType } from './file'; import type { MarkdownFileType } from './file';
import type { AuthorFileType } from './author.ts'; import type { AuthorFileType } from './author.ts';
import { getAllAuthors } from './author.ts'; import { getAllAuthors } from './author.ts';
import type { GuideFileType } from './guide.ts'; import { getCollection, type CollectionEntry } from 'astro:content';
import { getAllGuides } from './guide.ts';
export interface VideoFrontmatter { export type VideoFileType = CollectionEntry<'videos'> & {
title: string;
description: string;
authorId: string;
seo: {
title: string;
description: string;
};
isNew: boolean;
duration: string;
date: string;
sitemap: {
priority: number;
changefreq: 'daily' | 'weekly' | 'monthly' | 'yearly';
};
tags: string[];
}
export type VideoFileType = MarkdownFileType<VideoFrontmatter> & {
id: string;
author: AuthorFileType; author: AuthorFileType;
}; };
/**
* Generates id from the given video file
* @param filePath Markdown file path
*
* @returns unique video identifier
*/
function videoPathToId(filePath: string): string {
const fileName = filePath.split('/').pop() || '';
return fileName.replace('.md', '');
}
export async function getVideosByAuthor( export async function getVideosByAuthor(
authorId: string, authorId: string,
): Promise<VideoFileType[]> { ): Promise<VideoFileType[]> {
@ -52,43 +20,38 @@ export async function getVideosByAuthor(
* @returns Promisifed video files * @returns Promisifed video files
*/ */
export async function getAllVideos(): Promise<VideoFileType[]> { export async function getAllVideos(): Promise<VideoFileType[]> {
const videos = import.meta.glob<VideoFileType>('/src/data/videos/*.md', { const videoEntries = await getCollection('videos');
eager: true,
});
const allAuthors = await getAllAuthors(); const allAuthors = await getAllAuthors();
const videoFiles = Object.values(videos); const enrichedVideos = videoEntries.map((videoFile) => {
const enrichedVideos = videoFiles.map((videoFile) => ({ const author = allAuthors.find(
(author) => author.slug === videoFile.data.authorId,
);
if (!author) {
throw new Error(
`Author with ID ${videoFile.data.authorId} not found for video ${videoFile.data.title}`,
);
}
return {
...videoFile, ...videoFile,
id: videoPathToId(videoFile.file), author,
author: allAuthors.find( };
(author) => author.slug === videoFile.frontmatter.authorId, });
)!,
}));
return enrichedVideos.sort( return enrichedVideos.sort(
(a, b) => (a, b) => new Date(b.data.date).valueOf() - new Date(a.data.date).valueOf(),
new Date(b.frontmatter.date).valueOf() -
new Date(a.frontmatter.date).valueOf(),
); );
} }
export async function getVideoById(id: string): Promise<VideoFileType> { export async function getVideoById(id: string): Promise<VideoFileType> {
const videoFilesMap: Record<string, VideoFileType> = const allVideos = await getAllVideos();
import.meta.glob<VideoFileType>('../data/videos/*.md', { const videoFile = allVideos.find((video) => video.slug === id);
eager: true,
});
const videoFile = Object.values(videoFilesMap).find((videoFile) => {
return videoPathToId(videoFile.file) === id;
});
if (!videoFile) { if (!videoFile) {
throw new Error(`Video with ID ${id} not found`); throw new Error(`Video with ID ${id} not found`);
} }
return { return videoFile;
...videoFile,
id: videoPathToId(videoFile.file),
};
} }

@ -50,9 +50,9 @@ export async function GET() {
group: 'Guides', group: 'Guides',
})), })),
...videos.map((video) => ({ ...videos.map((video) => ({
id: video.id, id: video.slug,
url: `/videos/${video.id}`, url: `/videos/${video.slug}`,
title: video.frontmatter.title, title: video.data.title,
group: 'Videos', group: 'Videos',
})), })),
...projects.map((project) => ({ ...projects.map((project) => ({

@ -1,7 +1,7 @@
--- ---
import VideoHeader from '../../components/VideoHeader.astro'; import VideoHeader from '../../components/VideoHeader.astro';
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
import { getAllVideos, VideoFileType } from '../../lib/video'; import { getAllVideos, type VideoFileType } from '../../lib/video';
export interface Props { export interface Props {
video: VideoFileType; video: VideoFileType;
@ -11,18 +11,19 @@ export async function getStaticPaths() {
const videos = await getAllVideos(); const videos = await getAllVideos();
return videos.map((video) => ({ return videos.map((video) => ({
params: { videoId: video.id }, params: { videoId: video.slug },
props: { video }, props: { video },
})); }));
} }
const { videoId } = Astro.params; const { videoId } = Astro.params;
const { video } = Astro.props; const { video } = Astro.props;
const { Content } = await video.render();
--- ---
<BaseLayout <BaseLayout
title={video.frontmatter.title} title={video.data.title}
description={video.frontmatter.description} description={video.data.description}
permalink={`/videos/${videoId}`} permalink={`/videos/${videoId}`}
> >
<VideoHeader video={video} /> <VideoHeader video={video} />
@ -31,7 +32,7 @@ const { video } = Astro.props;
<div <div
class='container prose prose-h2:mb-2 prose-h2:mt-4 prose-h2:text-3xl prose-h3:mt-2 prose-code:bg-transparent prose-img:mt-1' class='container prose prose-h2:mb-2 prose-h2:mt-4 prose-h2:text-3xl prose-h3:mt-2 prose-code:bg-transparent prose-img:mt-1'
> >
<video.Content /> <Content />
</div> </div>
</div> </div>
</BaseLayout> </BaseLayout>

Loading…
Cancel
Save