From f79d8c05628c3fadf6a22fd3f3fd786e2965d01d Mon Sep 17 00:00:00 2001 From: Arik Chakma Date: Mon, 18 Aug 2025 21:32:31 +0600 Subject: [PATCH] refactor: roadmap specific guides (#9043) * fix: ai course generate url * wip * wip * wip * feat: roadmap guides * wip * wip * feat: featured guide list --- .../FeaturedGuides/FeaturedGuideList.tsx | 20 +- .../FeaturedGuides/GuideListItem.tsx | 52 +- src/components/Guide/GuideContent.astro | 72 --- src/components/Guide/GuideContent.tsx | 60 ++ src/components/Guide/RelatedGuides.tsx | 53 +- src/components/Questions/QuestionCard.tsx | 36 +- src/components/Questions/QuestionGuide.astro | 8 +- src/components/Questions/QuestionsList.tsx | 27 +- .../TableOfContent/TableOfContent.tsx | 6 +- src/lib/guide-renderer.tsx | 515 ++++++++++++++++++ src/lib/open-graph.ts | 25 +- src/lib/query-http.ts | 10 +- src/pages/[roadmapId]/[...topicId].astro | 141 +++-- src/pages/ai-data-scientist/career-path.astro | 30 - src/pages/ai-data-scientist/lifecycle.astro | 29 - src/pages/ai-data-scientist/skills.astro | 29 - src/pages/ai-data-scientist/tools.astro | 30 - src/pages/ai-data-scientist/vs-ai.astro | 29 - .../vs-business-analytics.astro | 29 - .../vs-computer-science.astro | 30 - .../ai-data-scientist/vs-cyber-security.astro | 29 - .../ai-data-scientist/vs-data-analytics.astro | 30 - .../vs-data-engineering.astro | 30 - .../vs-machine-learning.astro | 30 - .../vs-software-engineering.astro | 29 - .../ai-data-scientist/vs-statistics.astro | 30 - src/pages/authors/[authorId].astro | 153 ------ src/pages/authors/[authorId].json.ts | 30 - src/pages/backend/developer-skills.astro | 29 - src/pages/backend/developer-tools.astro | 30 - src/pages/backend/frameworks.astro | 33 -- src/pages/backend/job-description.astro | 29 - src/pages/backend/languages.astro | 33 -- src/pages/backend/project-ideas.astro | 30 - src/pages/backend/technologies.astro | 33 -- src/pages/data-analyst/career-path.astro | 29 - src/pages/data-analyst/how-to-become.astro | 29 - src/pages/data-analyst/vs-data-engineer.astro | 29 - src/pages/devops/automation-tools.astro | 30 - src/pages/devops/automation.astro | 30 - src/pages/devops/best-practices.astro | 30 - src/pages/devops/career-path.astro | 30 - src/pages/devops/devops-engineer.astro | 32 -- src/pages/devops/devops-vs-sre.astro | 30 - .../how-to-become-devops-engineer.astro | 30 - src/pages/devops/job-description.astro | 30 - src/pages/devops/lifecycle.astro | 30 - src/pages/devops/principles.astro | 30 - src/pages/devops/shift-left-testing.astro | 30 - src/pages/devops/skills.astro | 30 - src/pages/devops/test-automation.astro | 30 - src/pages/devops/tools.astro | 30 - src/pages/devops/vs-agile.astro | 29 - src/pages/devops/vs-developer.astro | 30 - src/pages/devops/vs-devsecops.astro | 29 - src/pages/devops/vs-full-stack.astro | 30 - src/pages/frontend/developer-skills.astro | 30 - src/pages/frontend/frameworks.astro | 30 - .../how-to-become-frontend-developer.astro | 30 - src/pages/frontend/job-description.astro | 30 - src/pages/frontend/languages.astro | 30 - src/pages/frontend/technologies.astro | 32 -- .../frontend/web-developer-portfolio.astro | 30 - src/pages/full-stack/developer-skills.astro | 30 - src/pages/full-stack/how-to-become.astro | 30 - src/pages/full-stack/job-description.astro | 30 - src/pages/full-stack/vs-backend.astro | 30 - .../full-stack/vs-software-engineer.astro | 30 - src/pages/golang/rest-api.astro | 30 - src/pages/golang/vs-java.astro | 29 - src/pages/guides/[guideId].astro | 47 -- src/pages/guides/index.astro | 40 -- src/pages/index.astro | 20 +- src/pages/java/developer-skills.astro | 29 - src/pages/java/vs-javascript.astro | 29 - src/pages/java/vs-python.astro | 29 - src/pages/javascript/vs-typescript.astro | 29 - src/pages/og/guide/[slug].ts | 10 +- src/pages/sql/hard-to-learn.astro | 29 - src/pages/sql/how-long-to-learn.astro | 29 - src/pages/sql/vs-mysql.astro | 29 - src/pages/sql/vs-python.astro | 29 - src/queries/official-guide.ts | 113 ++++ 83 files changed, 912 insertions(+), 2407 deletions(-) delete mode 100644 src/components/Guide/GuideContent.astro create mode 100644 src/components/Guide/GuideContent.tsx create mode 100644 src/lib/guide-renderer.tsx delete mode 100644 src/pages/ai-data-scientist/career-path.astro delete mode 100644 src/pages/ai-data-scientist/lifecycle.astro delete mode 100644 src/pages/ai-data-scientist/skills.astro delete mode 100644 src/pages/ai-data-scientist/tools.astro delete mode 100644 src/pages/ai-data-scientist/vs-ai.astro delete mode 100644 src/pages/ai-data-scientist/vs-business-analytics.astro delete mode 100644 src/pages/ai-data-scientist/vs-computer-science.astro delete mode 100644 src/pages/ai-data-scientist/vs-cyber-security.astro delete mode 100644 src/pages/ai-data-scientist/vs-data-analytics.astro delete mode 100644 src/pages/ai-data-scientist/vs-data-engineering.astro delete mode 100644 src/pages/ai-data-scientist/vs-machine-learning.astro delete mode 100644 src/pages/ai-data-scientist/vs-software-engineering.astro delete mode 100644 src/pages/ai-data-scientist/vs-statistics.astro delete mode 100644 src/pages/authors/[authorId].astro delete mode 100644 src/pages/authors/[authorId].json.ts delete mode 100644 src/pages/backend/developer-skills.astro delete mode 100644 src/pages/backend/developer-tools.astro delete mode 100644 src/pages/backend/frameworks.astro delete mode 100644 src/pages/backend/job-description.astro delete mode 100644 src/pages/backend/languages.astro delete mode 100644 src/pages/backend/project-ideas.astro delete mode 100644 src/pages/backend/technologies.astro delete mode 100644 src/pages/data-analyst/career-path.astro delete mode 100644 src/pages/data-analyst/how-to-become.astro delete mode 100644 src/pages/data-analyst/vs-data-engineer.astro delete mode 100644 src/pages/devops/automation-tools.astro delete mode 100644 src/pages/devops/automation.astro delete mode 100644 src/pages/devops/best-practices.astro delete mode 100644 src/pages/devops/career-path.astro delete mode 100644 src/pages/devops/devops-engineer.astro delete mode 100644 src/pages/devops/devops-vs-sre.astro delete mode 100644 src/pages/devops/how-to-become-devops-engineer.astro delete mode 100644 src/pages/devops/job-description.astro delete mode 100644 src/pages/devops/lifecycle.astro delete mode 100644 src/pages/devops/principles.astro delete mode 100644 src/pages/devops/shift-left-testing.astro delete mode 100644 src/pages/devops/skills.astro delete mode 100644 src/pages/devops/test-automation.astro delete mode 100644 src/pages/devops/tools.astro delete mode 100644 src/pages/devops/vs-agile.astro delete mode 100644 src/pages/devops/vs-developer.astro delete mode 100644 src/pages/devops/vs-devsecops.astro delete mode 100644 src/pages/devops/vs-full-stack.astro delete mode 100644 src/pages/frontend/developer-skills.astro delete mode 100644 src/pages/frontend/frameworks.astro delete mode 100644 src/pages/frontend/how-to-become-frontend-developer.astro delete mode 100644 src/pages/frontend/job-description.astro delete mode 100644 src/pages/frontend/languages.astro delete mode 100644 src/pages/frontend/technologies.astro delete mode 100644 src/pages/frontend/web-developer-portfolio.astro delete mode 100644 src/pages/full-stack/developer-skills.astro delete mode 100644 src/pages/full-stack/how-to-become.astro delete mode 100644 src/pages/full-stack/job-description.astro delete mode 100644 src/pages/full-stack/vs-backend.astro delete mode 100644 src/pages/full-stack/vs-software-engineer.astro delete mode 100644 src/pages/golang/rest-api.astro delete mode 100644 src/pages/golang/vs-java.astro delete mode 100644 src/pages/guides/[guideId].astro delete mode 100644 src/pages/guides/index.astro delete mode 100644 src/pages/java/developer-skills.astro delete mode 100644 src/pages/java/vs-javascript.astro delete mode 100644 src/pages/java/vs-python.astro delete mode 100644 src/pages/javascript/vs-typescript.astro delete mode 100644 src/pages/sql/hard-to-learn.astro delete mode 100644 src/pages/sql/how-long-to-learn.astro delete mode 100644 src/pages/sql/vs-mysql.astro delete mode 100644 src/pages/sql/vs-python.astro create mode 100644 src/queries/official-guide.ts diff --git a/src/components/FeaturedGuides/FeaturedGuideList.tsx b/src/components/FeaturedGuides/FeaturedGuideList.tsx index 7c531a09c..c6b9ad62c 100644 --- a/src/components/FeaturedGuides/FeaturedGuideList.tsx +++ b/src/components/FeaturedGuides/FeaturedGuideList.tsx @@ -1,22 +1,18 @@ -import type { GuideFileType } from '../../lib/guide'; -import type { QuestionGroupType } from '../../lib/question-group'; +import type { OfficialGuideDocument } from '../../queries/official-guide'; import { GuideListItem } from './GuideListItem'; export interface FeaturedGuidesProps { heading: string; - guides: GuideFileType[]; - questions: QuestionGroupType[]; + guides: OfficialGuideDocument[]; + questions: OfficialGuideDocument[]; } export function FeaturedGuideList(props: FeaturedGuidesProps) { const { heading, guides, questions = [] } = props; - const sortedGuides: (QuestionGroupType | GuideFileType)[] = [ - ...guides, - ...questions, - ].sort((a, b) => { - const aDate = new Date(a.frontmatter.date as string); - const bDate = new Date(b.frontmatter.date as string); + const sortedGuides = [...guides, ...questions].sort((a, b) => { + const aDate = new Date(a.publishedAt ?? new Date()); + const bDate = new Date(b.publishedAt ?? new Date()); return bDate.getTime() - aDate.getTime(); }); @@ -27,7 +23,7 @@ export function FeaturedGuideList(props: FeaturedGuidesProps) {
{sortedGuides.map((guide) => ( - + ))}
@@ -48,4 +44,4 @@ export function FeaturedGuideList(props: FeaturedGuidesProps) { ); -} \ No newline at end of file +} diff --git a/src/components/FeaturedGuides/GuideListItem.tsx b/src/components/FeaturedGuides/GuideListItem.tsx index f33cb3535..9005b5c2e 100644 --- a/src/components/FeaturedGuides/GuideListItem.tsx +++ b/src/components/FeaturedGuides/GuideListItem.tsx @@ -1,52 +1,46 @@ -import type { GuideFileType, GuideFrontmatter } from '../../lib/guide'; -import { type QuestionGroupType } from '../../lib/question-group'; -import dayjs from 'dayjs'; +import { DateTime } from 'luxon'; +import { + getOfficialGuideHref, + type OfficialGuideDocument, +} from '../../queries/official-guide'; export interface GuideListItemProps { - guide: GuideFileType | QuestionGroupType; -} - -function isQuestionGroupType( - guide: GuideFileType | QuestionGroupType, -): guide is QuestionGroupType { - return (guide as QuestionGroupType).questions !== undefined; + guide: OfficialGuideDocument; } export function GuideListItem(props: GuideListItemProps) { const { guide } = props; - const { frontmatter, id } = guide; + const { title, slug, publishedAt, roadmapId } = guide; - let pageUrl = ''; - let guideType = ''; - - if (isQuestionGroupType(guide)) { - pageUrl = `/questions/${id}`; - guideType = 'Questions'; - } else { - const excludedBySlug = (frontmatter as GuideFrontmatter).excludedBySlug; - pageUrl = excludedBySlug ? excludedBySlug : `/guides/${id}`; - guideType = (frontmatter as GuideFrontmatter).type; + let guideType = 'Textual'; + if (roadmapId === 'questions') { + guideType = 'Question'; } - // Check if article is within the last 15 days - const isNew = frontmatter.date - ? dayjs().diff(dayjs(frontmatter.date), 'day') < 15 - : false; + const publishedAtDate = publishedAt + ? DateTime.fromJSDate(new Date(publishedAt)) + : null; + + const isNew = + publishedAtDate && DateTime.now().diff(publishedAtDate, 'days').days < 15; + const publishedAtMonth = publishedAtDate + ? publishedAtDate.toFormat('MMMM') + : ''; return ( - {frontmatter.title} + {title} {isNew && ( New  ·  - {frontmatter.date ? dayjs(frontmatter.date).format('MMMM') : ''} + {publishedAtMonth} )} diff --git a/src/components/Guide/GuideContent.astro b/src/components/Guide/GuideContent.astro deleted file mode 100644 index 61ce3b047..000000000 --- a/src/components/Guide/GuideContent.astro +++ /dev/null @@ -1,72 +0,0 @@ ---- -import { getGuideTableOfContent, type GuideFileType } from '../../lib/guide'; -import MarkdownFile from '../MarkdownFile.astro'; -import { TableOfContent } from '../TableOfContent/TableOfContent'; -import { RelatedGuides } from './RelatedGuides'; - -interface Props { - guide: GuideFileType; -} - -const { guide } = Astro.props; - -const allHeadings = guide.getHeadings(); -const tableOfContent = getGuideTableOfContent(allHeadings); - -const showTableOfContent = tableOfContent.length > 0; -const showRelatedGuides = - guide?.relatedGuides && Object.keys(guide?.relatedGuides).length > 0; -const { frontmatter: guideFrontmatter, author } = guide; ---- - -
- { - (showTableOfContent || showRelatedGuides) && ( -
- - -
- ) - } - -
- -

- {guideFrontmatter.title} -

-

- - {author.frontmatter.name} - {author.frontmatter.name} - - - -

- - -
-
diff --git a/src/components/Guide/GuideContent.tsx b/src/components/Guide/GuideContent.tsx new file mode 100644 index 000000000..9d81e6008 --- /dev/null +++ b/src/components/Guide/GuideContent.tsx @@ -0,0 +1,60 @@ +import { cn } from '../../lib/classname'; +import { guideRenderer } from '../../lib/guide-renderer'; +import type { OfficialGuideResponse } from '../../queries/official-guide'; +import { TableOfContent } from '../TableOfContent/TableOfContent'; +import { RelatedGuides } from './RelatedGuides'; + +type GuideContentProps = { + guide: OfficialGuideResponse; +}; + +export function GuideContent(props: GuideContentProps) { + const { guide } = props; + const content = guideRenderer.render(guide.content); + const tableOfContents = guideRenderer.tableOfContents(guide.content); + const showTableOfContent = tableOfContents.length > 0; + const hasRelatedGuides = + guide.relatedGuides && guide.relatedGuides.length > 0; + + return ( +
+ {(showTableOfContent || hasRelatedGuides) && ( +
+ {hasRelatedGuides && ( + + )} + + {showTableOfContent && } +
+ )} + +
+
+

+ {guide.title} +

+

+ + {guide.author?.name} + {guide.author?.name} + +

+ + {content} +
+
+
+ ); +} diff --git a/src/components/Guide/RelatedGuides.tsx b/src/components/Guide/RelatedGuides.tsx index fd30870bf..879bc8c74 100644 --- a/src/components/Guide/RelatedGuides.tsx +++ b/src/components/Guide/RelatedGuides.tsx @@ -1,10 +1,11 @@ import { ChevronDown } from 'lucide-react'; import { useState } from 'react'; +import type { OfficialGuideDocument } from '../../queries/official-guide'; import { cn } from '../../lib/classname'; type RelatedGuidesProps = { relatedTitle?: string; - relatedGuides: Record; + relatedGuides: Pick[]; }; export function RelatedGuides(props: RelatedGuidesProps) { @@ -12,14 +13,7 @@ export function RelatedGuides(props: RelatedGuidesProps) { const [isOpen, setIsOpen] = useState(false); - const relatedGuidesArray = Object.entries(relatedGuides).map( - ([title, url]) => ({ - title, - url, - }), - ); - - if (relatedGuidesArray.length === 0) { + if (relatedGuides.length === 0) { return null; } @@ -47,23 +41,32 @@ export function RelatedGuides(props: RelatedGuidesProps) { isOpen && 'block', )} > - {relatedGuidesArray.map((relatedGuide) => ( -
  • - { - if (!isOpen) { - return; - } + {relatedGuides.map((relatedGuide) => { + const { roadmapId, slug, title } = relatedGuide; + const href = roadmapId ? `/${roadmapId}/${slug}` : `/guides/${slug}`; - setIsOpen(false); - }} - > - {relatedGuide.title} - -
  • - ))} + const className = cn( + 'text-sm text-gray-500 no-underline hover:text-black max-lg:block max-lg:border-b max-lg:px-3 max-lg:py-1', + ); + + return ( +
  • + { + if (!isOpen) { + return; + } + + setIsOpen(false); + }} + > + {title} + +
  • + ); + })} ); diff --git a/src/components/Questions/QuestionCard.tsx b/src/components/Questions/QuestionCard.tsx index 0087ba3d3..5ab38dfc0 100644 --- a/src/components/Questions/QuestionCard.tsx +++ b/src/components/Questions/QuestionCard.tsx @@ -1,8 +1,6 @@ import { Fragment, useEffect, useRef, useState } from 'react'; -import type { QuestionType } from '../../lib/question-group'; -import { markdownToHtml } from '../../lib/markdown'; -import Prism from 'prismjs'; -import './PrismAtom.css'; +import { guideRenderer, type QuestionType } from '../../lib/guide-renderer'; +import { cn } from '../../lib/classname'; type QuestionCardProps = { question: QuestionType; @@ -20,8 +18,6 @@ export function QuestionCard(props: QuestionCardProps) { // width if the answer is visible and the question height is less than // the answer height if (isAnswerVisible) { - Prism.highlightAll(); - const answerHeight = answerRef.current?.clientHeight || 0; const questionHeight = questionRef.current?.clientHeight || 0; @@ -69,7 +65,7 @@ export function QuestionCard(props: QuestionCardProps) {
    -

    +

    {question.question}

    @@ -88,27 +84,15 @@ export function QuestionCard(props: QuestionCardProps) {
    - {!question.isLongAnswer && ( -
    p]:leading-relaxed sm:text-xl`} - dangerouslySetInnerHTML={{ - __html: markdownToHtml(question.answer, false), - }} - /> - )} +
    + {guideRenderer.render(question.answer)} +
    - {question.isLongAnswer && ( -
    - )}
    diff --git a/src/components/TableOfContent/TableOfContent.tsx b/src/components/TableOfContent/TableOfContent.tsx index a71d01bb8..2ce89d578 100644 --- a/src/components/TableOfContent/TableOfContent.tsx +++ b/src/components/TableOfContent/TableOfContent.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import type { HeadingGroupType } from '../../lib/guide'; +import type { HeadingGroupType } from '../../lib/guide-renderer'; import { ChevronDown } from 'lucide-react'; import { cn } from '../../lib/classname'; @@ -23,7 +23,7 @@ export function TableOfContent(props: TableOfContentProps) { return (
    {heading.children.length > 0 && ( -
      +
        {heading.children.map((children) => { return (
      1. diff --git a/src/lib/guide-renderer.tsx b/src/lib/guide-renderer.tsx new file mode 100644 index 000000000..ac60f8768 --- /dev/null +++ b/src/lib/guide-renderer.tsx @@ -0,0 +1,515 @@ +import { Fragment } from 'react'; +import type { JSX } from 'react/jsx-runtime'; +import type { JSONContent } from '@tiptap/core'; +import { slugify } from './slugger'; +import { + CodeBlockContent, + CodeBlockHeader, + CodeBlockItem, +} from '../components/Global/CodeBlock'; +import { QuestionsList } from '../components/Questions/QuestionsList'; + +export type HeadingType = { + level: number; + text: string; + slug: string; +}; + +export type HeadingGroupType = HeadingType & { children: HeadingType[] }; + +export type QuestionType = { + id: string; + question: string; + answer: JSONContent; + topics: string[]; +}; + +export interface MarkType { + [key: string]: any; + type: string; + attrs?: Record | undefined; +} + +export class GuideRenderer { + private marksOrder = ['underline', 'bold', 'italic', 'textStyle', 'link']; + + render(content: JSONContent): JSX.Element[] { + const nodes = content.content || []; + const jsxNodes = nodes + .map((node, index) => { + const component = this.renderNode(node); + if (!component) { + return null; + } + + return {component}; + }) + .filter(Boolean) as JSX.Element[]; + + return jsxNodes; + } + + tableOfContents(node: JSONContent) { + const headlines = this.headlines(node); + let toc: HeadingGroupType[] = []; + let currentGroup: HeadingGroupType | null = null; + + const hasQASection = node.content?.some( + (node) => node.type === 'qaSection', + ); + + headlines + .filter((heading) => heading.level !== 1) + .forEach((heading) => { + if (heading.level === 2) { + currentGroup = { ...heading, children: [] }; + toc.push(currentGroup); + } else if (currentGroup && heading.level === 3) { + currentGroup.children.push({ ...heading, text: heading.text }); + } + }); + + if (toc.length > 5) { + toc.forEach((group) => { + group.children = []; + }); + } + + const qaSection = node.content?.find((node) => node.type === 'qaSection'); + if (hasQASection && qaSection) { + toc.push({ + level: 2, + text: 'Test yourself with Flashcards', + slug: 'test-with-flashcards', + children: [], + }); + + const questions = this.questions(qaSection); + const topicsInOrder = [ + ...new Set( + questions + .map((question) => question.topics) + .flat() + .filter(Boolean), + ), + ]; + + toc.push({ + level: 2, + text: 'Questions List', + slug: 'questions-list', + children: topicsInOrder.map((topic) => { + let topicText = topic; + let topicSlug = slugify(topic); + if (topic.toLowerCase() === 'beginners') { + topicText = 'Beginner Level'; + topicSlug = 'beginner-level'; + } else if (topic.toLowerCase() === 'intermediate') { + topicText = 'Intermediate Level'; + topicSlug = 'intermediate-level'; + } else if (topic.toLowerCase() === 'advanced') { + topicText = 'Advanced Level'; + topicSlug = 'advanced-level'; + } + + return { + level: 2, + children: [], + slug: topicSlug, + text: topicText, + }; + }), + }); + } + + return toc; + } + + headlines(node: JSONContent) { + const nodes = node.content || []; + const headlines: Array = []; + + let hasFoundQASection = false; + const extractHeadlines = (node: JSONContent) => { + if (node.type === 'qaSection' || hasFoundQASection) { + hasFoundQASection = true; + return; + } + + if (node.type === 'heading') { + const text = this.getText(node); + headlines.push({ + level: node.attrs?.level || 1, + text, + slug: slugify(text), + }); + } + + if (node.content) { + node.content.forEach((childNode) => { + extractHeadlines(childNode); + }); + } + }; + + nodes.forEach((childNode) => { + extractHeadlines(childNode); + }); + + return headlines; + } + + private getText(node: JSONContent): string { + if (node.type === 'text') { + return node.text || ''; + } + + if (node.content) { + return node.content.map((childNode) => this.getText(childNode)).join(''); + } + + return ''; + } + + // `content` will call corresponding node type + // and return text content + private content(node: JSONContent): JSX.Element[] { + const allNodes = node.content || []; + return allNodes + .map((childNode, index) => { + const component = this.renderNode(childNode); + if (!component) { + return null; + } + + return ( + {component} + ); + }) + .filter(Boolean) as JSX.Element[]; + } + + // `renderNode` will call the method of the corresponding node type + private renderNode(node: JSONContent): JSX.Element | null { + const type = node.type || ''; + + if (type in this) { + // @ts-expect-error - `this` is not assignable to type 'never' + return this[type]?.(node) as JSX.Element; + } + + console.log(`Node type "${type}" is not supported.`); + return null; + } + + // `renderMark` will call the method of the corresponding mark type + private renderMark(node: JSONContent): JSX.Element { + // It will wrap the text with the corresponding mark type + const text = node?.text || <> ; + let marks = node?.marks || []; + // sort the marks by uderline, bold, italic, textStyle, link + // so that the text will be wrapped in the correct order + marks.sort((a, b) => { + return this.marksOrder.indexOf(a.type) - this.marksOrder.indexOf(b.type); + }); + + return marks.reduce( + (acc, mark) => { + const type = mark.type; + if (type in this) { + // @ts-expect-error - `this` is not assignable to type 'never' + return this[type]?.(mark, acc) as JSX.Element; + } + + throw new Error(`Mark type "${type}" is not supported.`); + }, + <>{text}, + ); + } + + private paragraph(node: JSONContent): JSX.Element { + const isEmpty = + !node.content || + node.content?.every( + (child) => child.type === 'text' && child.text === '', + ); + if (isEmpty) { + return <>; + } + + return

        {this.content(node)}

        ; + } + + private text(node: JSONContent): JSX.Element { + if (node.marks) { + return this.renderMark(node); + } + + const text = node.text; + return text ? <>{text} : <> ; + } + + private bold(_: MarkType, text: JSX.Element): JSX.Element { + return {text}; + } + + private italic(_: MarkType, text: JSX.Element): JSX.Element { + return {text}; + } + + private underline(_: MarkType, text: JSX.Element): JSX.Element { + return {text}; + } + + private strike(_: MarkType, text: JSX.Element): JSX.Element { + return {text}; + } + + private textStyle(mark: MarkType, text: JSX.Element): JSX.Element { + const { attrs } = mark; + const { color = 'inherit' } = attrs || {}; + + return ( + + {text} + + ); + } + + private link(mark: MarkType, text: JSX.Element): JSX.Element { + const { attrs } = mark; + const { href } = attrs || {}; + + const isExternal = href?.startsWith('http'); + const isRoadmapUrl = href?.startsWith('https://roadmap.sh/'); + + const rel = isExternal && !isRoadmapUrl ? 'noopener noreferrer' : undefined; + + return ( + + {text} + + ); + } + + private heading(node: JSONContent): JSX.Element { + const { attrs } = node; + const { level } = attrs || {}; + + const text = this.getText(node); + const slug = slugify(text); + + let Comp: keyof JSX.IntrinsicElements = 'h1'; + if (level === 2) { + Comp = 'h2'; + } else if (level === 3) { + Comp = 'h3'; + } else if (level === 4) { + Comp = 'h4'; + } else if (level === 5) { + Comp = 'h5'; + } else if (level === 6) { + Comp = 'h6'; + } + + return {this.content(node)}; + } + + private horizontalRule(_: JSONContent): JSX.Element { + return
        ; + } + + private orderedList(node: JSONContent): JSX.Element { + return
          {this.content(node)}
        ; + } + + private bulletList(node: JSONContent): JSX.Element { + return
          {this.content(node)}
        ; + } + + private listItem(node: JSONContent): JSX.Element { + return
      2. {this.content(node)}
      3. ; + } + + private hardBreak(_: JSONContent): JSX.Element { + return
        ; + } + + private image(node: JSONContent): JSX.Element { + const { attrs } = node; + const { src, alt } = attrs || {}; + + return {alt; + } + + private code(_: MarkType, text: JSX.Element): JSX.Element { + return {text}; + } + + private codeBlock(node: JSONContent): JSX.Element { + const code = this.getText(node); + const language = node.attrs?.language || 'javascript'; + + return ( +
        + + + + {code} + +
        + ); + } + + private blockquote(node: JSONContent): JSX.Element { + return
        {this.content(node)}
        ; + } + + questions(node: JSONContent) { + const content = node.content || []; + const questions: QuestionType[] = []; + let currentTopic: string | null = null; + let currentQuestion: QuestionType | null = null; + + for (const childNode of content) { + switch (childNode.type) { + case 'heading': + // if level is 2, it's a topic + if (childNode.attrs?.level === 2) { + currentTopic = this.getText(childNode); + // if level is 3, it's a question + } else if (childNode.attrs?.level === 3) { + if (currentTopic) { + const questionText = this.getText(childNode); + currentQuestion = { + id: slugify(questionText), + question: questionText, + answer: { + type: 'doc', + content: [], + }, + topics: [currentTopic], + }; + questions.push(currentQuestion); + } + } + break; + // anything else is an answer + default: + if (!currentQuestion || !currentQuestion.answer.content) { + console.warn('No current question found'); + continue; + } + + currentQuestion.answer.content.push(childNode); + break; + } + } + + return questions; + } + + private qaSection(node: JSONContent): JSX.Element { + const questions = this.questions(node); + + const questionsGroupedByTopics = questions.reduce( + (acc, question) => { + question.topics?.forEach((topic) => { + acc[topic] = [...(acc[topic] || []), question]; + }); + return acc; + }, + {} as Record, + ); + + const topicsInOrder = [ + ...new Set( + questions + .map((question) => question.topics) + .flat() + .filter(Boolean), + ), + ]; + + return ( + <> +

        Test yourself with Flashcards

        +

        + You can either use these flashcards or jump to the questions list + section below to see them in a list format. +

        + +
        + +
        + +

        Questions List

        +

        + If you prefer to see the questions in a list format, you can find them + below. +

        + + {topicsInOrder.map((questionLevel) => ( +
        +

        + {questionLevel.toLowerCase() === 'beginners' + ? 'Beginner Level' + : questionLevel.toLowerCase() === 'intermediate' + ? 'Intermediate Level' + : questionLevel.toLowerCase() === 'advanced' + ? 'Advanced Level' + : questionLevel} +

        + {questionsGroupedByTopics[questionLevel].map((q) => ( +
        +

        {q.question}

        +
        {this.render(q.answer)}
        +
        + ))} +
        + ))} + + ); + } + + private table(node: JSONContent): JSX.Element { + const content = node.content || []; + const rows = content.filter((node) => node.type === 'tableRow'); + const firstRow = rows?.[0]; + const hasTableHead = firstRow?.content?.some( + (node) => node.type === 'tableHeader', + ); + + const remainingRows = rows.slice(hasTableHead ? 1 : 0); + + return ( + + {hasTableHead && {this.renderNode(firstRow)}} + + {this.render({ + type: 'doc', + content: remainingRows, + })} + +
        + ); + } + + private tableRow(node: JSONContent): JSX.Element { + return {this.content(node)}; + } + + private tableHeader(node: JSONContent): JSX.Element { + return {this.content(node)}; + } + + private tableCell(node: JSONContent): JSX.Element { + return {this.content(node)}; + } +} + +export const guideRenderer = new GuideRenderer(); diff --git a/src/lib/open-graph.ts b/src/lib/open-graph.ts index b4debfe7a..020753fcb 100644 --- a/src/lib/open-graph.ts +++ b/src/lib/open-graph.ts @@ -3,8 +3,22 @@ type RoadmapOpenGraphQuery = { resourceId: string; }; -export function getOpenGraphImageUrl(params: RoadmapOpenGraphQuery) { - return `${import.meta.env.DEV ? 'http://localhost:3000' : 'https://roadmap.sh'}/og/${params.group}/${params.resourceId}`; +export function getOpenGraphImageUrl( + params: RoadmapOpenGraphQuery, + query?: Record, +) { + const baseUrl = import.meta.env.DEV + ? 'http://localhost:3000' + : 'https://roadmap.sh'; + + const url = new URL(`${baseUrl}/og/${params.group}/${params.resourceId}`); + if (query) { + Object.entries(query).forEach(([key, value]) => { + url.searchParams.set(key, value); + }); + } + + return String(url); } export async function getDefaultOpenGraphImageBuffer() { @@ -15,11 +29,18 @@ export async function getDefaultOpenGraphImageBuffer() { export async function getResourceOpenGraph( type: 'roadmap' | 'guide' | 'best-practice', resourceId: string, + query?: Record, ) { const url = new URL(`${import.meta.env.PUBLIC_API_URL}/v1-open-graph`); url.searchParams.set('type', type); url.searchParams.set('resourceId', resourceId); url.searchParams.set('variant', 'image'); + if (query) { + Object.entries(query).forEach(([key, value]) => { + url.searchParams.set(key, value); + }); + } + const response = await fetch(url.toString()); return response.text(); diff --git a/src/lib/query-http.ts b/src/lib/query-http.ts index 70e8cec75..a37fe1a5c 100644 --- a/src/lib/query-http.ts +++ b/src/lib/query-http.ts @@ -43,15 +43,19 @@ export async function httpCall( ? url : `${import.meta.env.PUBLIC_API_URL}${url}`; try { - const fingerprintPromise = await fp.load(); - const fingerprint = await fingerprintPromise.get(); + let visitorId = ''; + if (typeof window !== 'undefined') { + const fingerprintPromise = await fp.load(); + const fingerprint = await fingerprintPromise.get(); + visitorId = fingerprint.visitorId; + } const isMultiPartFormData = options?.body instanceof FormData; const headers = new Headers({ Accept: 'application/json', Authorization: `Bearer ${Cookies.get(TOKEN_COOKIE_NAME)}`, - fp: fingerprint.visitorId, + ...(visitorId ? { fp: visitorId } : {}), ...(options?.headers ?? {}), }); diff --git a/src/pages/[roadmapId]/[...topicId].astro b/src/pages/[roadmapId]/[...topicId].astro index 8682a1e2d..488df4c2f 100644 --- a/src/pages/[roadmapId]/[...topicId].astro +++ b/src/pages/[roadmapId]/[...topicId].astro @@ -4,6 +4,13 @@ import path from 'node:path'; import matter from 'gray-matter'; import MarkdownIt from 'markdown-it-async'; import { fileURLToPath } from 'node:url'; +import { + getOfficialGuide, + type OfficialGuideResponse, +} from '../../queries/official-guide'; +import BaseLayout from '../../layouts/BaseLayout.astro'; +import { GuideContent } from '../../components/Guide/GuideContent'; +import { getOpenGraphImageUrl } from '../../lib/open-graph'; export const prerender = false; @@ -16,72 +23,108 @@ if (!topicId || !roadmapId) { return Astro.rewrite('/404'); } -// Handle nested paths by joining the segments -const topicPath = Array.isArray(topicId) ? topicId.join('/') : topicId; +const isTopic = topicId?.includes('@') || topicId?.includes('/'); +let gitHubUrl = ''; +let htmlContent = ''; +let guide: OfficialGuideResponse | null = null; +let permalink = ''; +let ogImageUrl = ''; -// Get the project root directory -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); +if (isTopic) { + // Handle nested paths by joining the segments + const topicPath = Array.isArray(topicId) ? topicId.join('/') : topicId; -// hack to make it work. TODO: Fix -const projectRoot = path.resolve(__dirname, '../../..').replace(/dist$/, ''); + // Get the project root directory + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); -// Construct the path to the markdown file -let contentPath = path.join( - projectRoot, - 'src', - 'data', - 'roadmaps', - roadmapId, - 'content', - `${topicPath}.md`, -); + // hack to make it work. TODO: Fix + const projectRoot = path.resolve(__dirname, '../../..').replace(/dist$/, ''); -// Check if file exists -if (!fs.existsSync(contentPath)) { - const indexFilePath = path.join( + // Construct the path to the markdown file + let contentPath = path.join( projectRoot, 'src', 'data', 'roadmaps', roadmapId, 'content', - `${topicPath}/index.md`, + `${topicPath}.md`, ); - if (!fs.existsSync(indexFilePath)) { + // Check if file exists + if (!fs.existsSync(contentPath)) { + const indexFilePath = path.join( + projectRoot, + 'src', + 'data', + 'roadmaps', + roadmapId, + 'content', + `${topicPath}/index.md`, + ); + + if (!fs.existsSync(indexFilePath)) { + Astro.response.status = 404; + Astro.response.statusText = 'Not found'; + + return Astro.rewrite('/404'); + } + + contentPath = indexFilePath; + } + + // Read and parse the markdown file + const fileContent = fs.readFileSync(contentPath, 'utf-8'); + const { content } = matter(fileContent); + + const fileWithoutBasePath = contentPath.replace( + /.+?\/src\/data/, + '/src/data', + ); + + const md = MarkdownIt(); + + gitHubUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/master${fileWithoutBasePath}`; + htmlContent = await md.renderAsync(content); +} else { + guide = await getOfficialGuide(topicId, roadmapId); + if (!guide) { Astro.response.status = 404; Astro.response.statusText = 'Not found'; - return Astro.rewrite('/404'); } - contentPath = indexFilePath; + permalink = `/${roadmapId}/${topicId}`; + ogImageUrl = + guide?.featuredImage || + getOpenGraphImageUrl( + { + group: 'guide', + resourceId: topicId, + }, + { + roadmapId, + }, + ); } - -// Read and parse the markdown file -const fileContent = fs.readFileSync(contentPath, 'utf-8'); -const { data: frontmatter, content } = matter(fileContent); - -// Get the roadmap metadata -const roadmapPath = path.join( - projectRoot, - 'src', - 'data', - 'roadmaps', - roadmapId, - `${roadmapId}.md`, -); -const roadmapContent = fs.readFileSync(roadmapPath, 'utf-8'); -const { data: roadmapData } = matter(roadmapContent); - -const fileWithoutBasePath = contentPath.replace(/.+?\/src\/data/, '/src/data'); -const gitHubUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/master${fileWithoutBasePath}`; - -const md = MarkdownIt(); -const htmlContent = await md.renderAsync(content); --- -
        - - +{ + isTopic ? ( + <> +
        + + + ) : ( + + +
        + + ) +} diff --git a/src/pages/ai-data-scientist/career-path.astro b/src/pages/ai-data-scientist/career-path.astro deleted file mode 100644 index bca35672d..000000000 --- a/src/pages/ai-data-scientist/career-path.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'ai-data-scientist-career-path'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/ai-data-scientist/lifecycle.astro b/src/pages/ai-data-scientist/lifecycle.astro deleted file mode 100644 index 9347bca71..000000000 --- a/src/pages/ai-data-scientist/lifecycle.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'ai-data-scientist-lifecycle'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/ai-data-scientist/skills.astro b/src/pages/ai-data-scientist/skills.astro deleted file mode 100644 index bc3e50c76..000000000 --- a/src/pages/ai-data-scientist/skills.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'ai-data-scientist-skills'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/ai-data-scientist/tools.astro b/src/pages/ai-data-scientist/tools.astro deleted file mode 100644 index 433b6a9fe..000000000 --- a/src/pages/ai-data-scientist/tools.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'ai-data-scientist-tools'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/ai-data-scientist/vs-ai.astro b/src/pages/ai-data-scientist/vs-ai.astro deleted file mode 100644 index fa7a21766..000000000 --- a/src/pages/ai-data-scientist/vs-ai.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'ai-data-scientist-vs-ai'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/ai-data-scientist/vs-business-analytics.astro b/src/pages/ai-data-scientist/vs-business-analytics.astro deleted file mode 100644 index a1fc61f66..000000000 --- a/src/pages/ai-data-scientist/vs-business-analytics.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'ai-data-scientist-vs-business-analytics'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/ai-data-scientist/vs-computer-science.astro b/src/pages/ai-data-scientist/vs-computer-science.astro deleted file mode 100644 index 6df46bde1..000000000 --- a/src/pages/ai-data-scientist/vs-computer-science.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'ai-data-scientist-vs-computer-science'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/ai-data-scientist/vs-cyber-security.astro b/src/pages/ai-data-scientist/vs-cyber-security.astro deleted file mode 100644 index 216ef2390..000000000 --- a/src/pages/ai-data-scientist/vs-cyber-security.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'ai-data-scientist-vs-cyber-security'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/ai-data-scientist/vs-data-analytics.astro b/src/pages/ai-data-scientist/vs-data-analytics.astro deleted file mode 100644 index 1df2ac4b9..000000000 --- a/src/pages/ai-data-scientist/vs-data-analytics.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'ai-data-scientist-vs-data-analytics'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/ai-data-scientist/vs-data-engineering.astro b/src/pages/ai-data-scientist/vs-data-engineering.astro deleted file mode 100644 index abd369879..000000000 --- a/src/pages/ai-data-scientist/vs-data-engineering.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'ai-data-scientist-vs-data-engineering'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/ai-data-scientist/vs-machine-learning.astro b/src/pages/ai-data-scientist/vs-machine-learning.astro deleted file mode 100644 index b9f38bbcf..000000000 --- a/src/pages/ai-data-scientist/vs-machine-learning.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'ai-data-scientist-vs-machine-learning'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/ai-data-scientist/vs-software-engineering.astro b/src/pages/ai-data-scientist/vs-software-engineering.astro deleted file mode 100644 index 385b72467..000000000 --- a/src/pages/ai-data-scientist/vs-software-engineering.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'ai-data-scientist-vs-software-engineering'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/ai-data-scientist/vs-statistics.astro b/src/pages/ai-data-scientist/vs-statistics.astro deleted file mode 100644 index f67188ee0..000000000 --- a/src/pages/ai-data-scientist/vs-statistics.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'ai-data-scientist-vs-statistics'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/authors/[authorId].astro b/src/pages/authors/[authorId].astro deleted file mode 100644 index 2184e8e44..000000000 --- a/src/pages/authors/[authorId].astro +++ /dev/null @@ -1,153 +0,0 @@ ---- -import AstroIcon from '../../components/AstroIcon.astro'; -import { GuideListItem } from '../../components/FeaturedGuides/GuideListItem'; -import { VideoListItem } from '../../components/FeaturedVideos/VideoListItem'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getAuthorById, getAuthorIds } from '../../lib/author'; -import { getGuidesByAuthor } from '../../lib/guide'; -import { getAllQuestionGroups } from '../../lib/question-group'; -import { getVideosByAuthor } from '../../lib/video'; - -export const prerender = true; - -interface Params extends Record {} - -export async function getStaticPaths() { - const authorIds = await getAuthorIds(); - - return authorIds.map((authorId) => ({ - params: { authorId }, - })); -} - -const { authorId } = Astro.params; - -const author = await getAuthorById(authorId); -const authorFrontmatter = author.frontmatter; - -const guides = await getGuidesByAuthor(authorId); -const questionGuides = (await getAllQuestionGroups()).filter( - (group) => group.frontmatter.authorId === authorId, -); -const videos = await getVideosByAuthor(authorId); ---- - - -
        -
        -
        -
        -

        - {authorFrontmatter.name} -

        -
        - -
        - -
        -
        - { - authorFrontmatter.social?.github && ( - - - - ) - } - { - authorFrontmatter.social.twitter && ( - - - - ) - } - { - authorFrontmatter.social.linkedin && ( - - - - ) - } - { - authorFrontmatter.social.website && ( - - - - ) - } -
        -
        -
        - -
        -
        -
        - { - [...guides, ...questionGuides] - .sort((a, b) => { - const aFrontmatter = a.frontmatter as any; - const bFrontmatter = b.frontmatter as any; - - const aDate = aFrontmatter.date || aFrontmatter.publishedAt; - const bDate = bFrontmatter.date || bFrontmatter.publishedAt; - return new Date(bDate).getTime() - new Date(aDate).getTime(); - }) - .map((guide) => ) - } - {videos.map((video) => )} -
        -
        -
        diff --git a/src/pages/authors/[authorId].json.ts b/src/pages/authors/[authorId].json.ts deleted file mode 100644 index 43bf20dbf..000000000 --- a/src/pages/authors/[authorId].json.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { APIRoute } from 'astro'; -import { getAuthorById, getAuthorIds } from '../../lib/author'; - -export const prerender = true; - -export async function getStaticPaths() { - const authorIds = await getAuthorIds(); - - return await Promise.all( - authorIds.map(async (authorId) => { - const authorDetails = await getAuthorById(authorId); - - return { - params: { authorId }, - props: { - authorDetails: authorDetails?.frontmatter || {}, - }, - }; - }), - ); -} - -export const GET: APIRoute = async function ({ params, request, props }) { - return new Response(JSON.stringify(props.authorDetails), { - status: 200, - headers: { - 'Content-Type': 'application/json', - }, - }); -}; diff --git a/src/pages/backend/developer-skills.astro b/src/pages/backend/developer-skills.astro deleted file mode 100644 index 3010e657e..000000000 --- a/src/pages/backend/developer-skills.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'backend-developer-skills'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/backend/developer-tools.astro b/src/pages/backend/developer-tools.astro deleted file mode 100644 index d00f49ca1..000000000 --- a/src/pages/backend/developer-tools.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'backend-developer-tools'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/backend/frameworks.astro b/src/pages/backend/frameworks.astro deleted file mode 100644 index aef48f7cb..000000000 --- a/src/pages/backend/frameworks.astro +++ /dev/null @@ -1,33 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'backend-frameworks'; -const guide = await getGuideById(guideId).catch(() => null); -if (!guide) { - return Astro.redirect('/404'); -} - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/backend/job-description.astro b/src/pages/backend/job-description.astro deleted file mode 100644 index 61af69fca..000000000 --- a/src/pages/backend/job-description.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'backend-job-description'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo?.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - - diff --git a/src/pages/backend/languages.astro b/src/pages/backend/languages.astro deleted file mode 100644 index cd00a4719..000000000 --- a/src/pages/backend/languages.astro +++ /dev/null @@ -1,33 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'backend-languages'; -const guide = await getGuideById(guideId).catch(() => null); -if (!guide) { - return Astro.redirect('/404'); -} - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/backend/project-ideas.astro b/src/pages/backend/project-ideas.astro deleted file mode 100644 index c9d714942..000000000 --- a/src/pages/backend/project-ideas.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'backend-project-ideas'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/backend/technologies.astro b/src/pages/backend/technologies.astro deleted file mode 100644 index 0efcd5018..000000000 --- a/src/pages/backend/technologies.astro +++ /dev/null @@ -1,33 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'backend-technologies'; -const guide = await getGuideById(guideId).catch(() => null); -if (!guide) { - return Astro.redirect('/404'); -} - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/data-analyst/career-path.astro b/src/pages/data-analyst/career-path.astro deleted file mode 100644 index c4f48dbf5..000000000 --- a/src/pages/data-analyst/career-path.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'data-analyst-career-path'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/data-analyst/how-to-become.astro b/src/pages/data-analyst/how-to-become.astro deleted file mode 100644 index 2d4e2fac6..000000000 --- a/src/pages/data-analyst/how-to-become.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'data-analyst-how-to-become'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/data-analyst/vs-data-engineer.astro b/src/pages/data-analyst/vs-data-engineer.astro deleted file mode 100644 index 71b7f9452..000000000 --- a/src/pages/data-analyst/vs-data-engineer.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'data-analyst-vs-data-engineer'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        \ No newline at end of file diff --git a/src/pages/devops/automation-tools.astro b/src/pages/devops/automation-tools.astro deleted file mode 100644 index e1b18f847..000000000 --- a/src/pages/devops/automation-tools.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'devops-automation-tools'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/devops/automation.astro b/src/pages/devops/automation.astro deleted file mode 100644 index c6f1a3d36..000000000 --- a/src/pages/devops/automation.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'devops-automation'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/devops/best-practices.astro b/src/pages/devops/best-practices.astro deleted file mode 100644 index e0ce7f19f..000000000 --- a/src/pages/devops/best-practices.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'devops-best-practices'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/devops/career-path.astro b/src/pages/devops/career-path.astro deleted file mode 100644 index 592f9ea57..000000000 --- a/src/pages/devops/career-path.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'devops-career-path'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/devops/devops-engineer.astro b/src/pages/devops/devops-engineer.astro deleted file mode 100644 index abf33c5a8..000000000 --- a/src/pages/devops/devops-engineer.astro +++ /dev/null @@ -1,32 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -export const prerender = false; - -const guideId = 'devops-engineer'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/devops/devops-vs-sre.astro b/src/pages/devops/devops-vs-sre.astro deleted file mode 100644 index b8a83f091..000000000 --- a/src/pages/devops/devops-vs-sre.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'devops-vs-sre'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/devops/how-to-become-devops-engineer.astro b/src/pages/devops/how-to-become-devops-engineer.astro deleted file mode 100644 index e0d333c09..000000000 --- a/src/pages/devops/how-to-become-devops-engineer.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { replaceVariables } from '../../lib/markdown'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'how-to-become-devops-engineer'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/devops/job-description.astro b/src/pages/devops/job-description.astro deleted file mode 100644 index e6455932d..000000000 --- a/src/pages/devops/job-description.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'devops-job-description'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/devops/lifecycle.astro b/src/pages/devops/lifecycle.astro deleted file mode 100644 index cdf5c514d..000000000 --- a/src/pages/devops/lifecycle.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'devops-lifecycle'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/devops/principles.astro b/src/pages/devops/principles.astro deleted file mode 100644 index 6bad64ced..000000000 --- a/src/pages/devops/principles.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'devops-principles'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/devops/shift-left-testing.astro b/src/pages/devops/shift-left-testing.astro deleted file mode 100644 index 439e7ffb5..000000000 --- a/src/pages/devops/shift-left-testing.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'devops-shift-left-testing'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/devops/skills.astro b/src/pages/devops/skills.astro deleted file mode 100644 index b067817a8..000000000 --- a/src/pages/devops/skills.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { replaceVariables } from '../../lib/markdown'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'devops-skills'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/devops/test-automation.astro b/src/pages/devops/test-automation.astro deleted file mode 100644 index b03226944..000000000 --- a/src/pages/devops/test-automation.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'devops-test-automation'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/devops/tools.astro b/src/pages/devops/tools.astro deleted file mode 100644 index 2d4be2ac2..000000000 --- a/src/pages/devops/tools.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'devops-tools'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/devops/vs-agile.astro b/src/pages/devops/vs-agile.astro deleted file mode 100644 index fa497f10b..000000000 --- a/src/pages/devops/vs-agile.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'devops-vs-agile'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/devops/vs-developer.astro b/src/pages/devops/vs-developer.astro deleted file mode 100644 index 08c31adf0..000000000 --- a/src/pages/devops/vs-developer.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'devops-vs-developer'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/devops/vs-devsecops.astro b/src/pages/devops/vs-devsecops.astro deleted file mode 100644 index c4047865b..000000000 --- a/src/pages/devops/vs-devsecops.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'devops-vs-devsecops'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/devops/vs-full-stack.astro b/src/pages/devops/vs-full-stack.astro deleted file mode 100644 index 0cff40988..000000000 --- a/src/pages/devops/vs-full-stack.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { replaceVariables } from '../../lib/markdown'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'devops-vs-full-stack'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/frontend/developer-skills.astro b/src/pages/frontend/developer-skills.astro deleted file mode 100644 index dd7c0b5e7..000000000 --- a/src/pages/frontend/developer-skills.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { replaceVariables } from '../../lib/markdown'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'frontend-developer-skills'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/frontend/frameworks.astro b/src/pages/frontend/frameworks.astro deleted file mode 100644 index fd535680e..000000000 --- a/src/pages/frontend/frameworks.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { replaceVariables } from '../../lib/markdown'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'frontend-frameworks'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/frontend/how-to-become-frontend-developer.astro b/src/pages/frontend/how-to-become-frontend-developer.astro deleted file mode 100644 index 353f86660..000000000 --- a/src/pages/frontend/how-to-become-frontend-developer.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'how-to-become-frontend-developer'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/frontend/job-description.astro b/src/pages/frontend/job-description.astro deleted file mode 100644 index 5aaa73729..000000000 --- a/src/pages/frontend/job-description.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'frontend-job-description'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo?.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/frontend/languages.astro b/src/pages/frontend/languages.astro deleted file mode 100644 index ee1ada208..000000000 --- a/src/pages/frontend/languages.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { replaceVariables } from '../../lib/markdown'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'frontend-languages'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/frontend/technologies.astro b/src/pages/frontend/technologies.astro deleted file mode 100644 index 4477e9304..000000000 --- a/src/pages/frontend/technologies.astro +++ /dev/null @@ -1,32 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'frontend-technologies'; -const guide = await getGuideById(guideId).catch(() => null); -if (!guide) { - return Astro.redirect('/404'); -} - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/frontend/web-developer-portfolio.astro b/src/pages/frontend/web-developer-portfolio.astro deleted file mode 100644 index 63fe99b9e..000000000 --- a/src/pages/frontend/web-developer-portfolio.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'frontend-web-developer-portfolio'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/full-stack/developer-skills.astro b/src/pages/full-stack/developer-skills.astro deleted file mode 100644 index d48ecd76d..000000000 --- a/src/pages/full-stack/developer-skills.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'full-stack-developer-skills'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/full-stack/how-to-become.astro b/src/pages/full-stack/how-to-become.astro deleted file mode 100644 index e23b2b38c..000000000 --- a/src/pages/full-stack/how-to-become.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'full-stack-how-to-become'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/full-stack/job-description.astro b/src/pages/full-stack/job-description.astro deleted file mode 100644 index d963039fa..000000000 --- a/src/pages/full-stack/job-description.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'full-stack-job-description'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/full-stack/vs-backend.astro b/src/pages/full-stack/vs-backend.astro deleted file mode 100644 index 617918749..000000000 --- a/src/pages/full-stack/vs-backend.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'full-stack-vs-backend'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/full-stack/vs-software-engineer.astro b/src/pages/full-stack/vs-software-engineer.astro deleted file mode 100644 index 6d04fa25d..000000000 --- a/src/pages/full-stack/vs-software-engineer.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'full-stack-vs-software-engineer'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/golang/rest-api.astro b/src/pages/golang/rest-api.astro deleted file mode 100644 index 90acda95e..000000000 --- a/src/pages/golang/rest-api.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -const guideId = 'golang-rest-api'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/golang/vs-java.astro b/src/pages/golang/vs-java.astro deleted file mode 100644 index c8ced7e9d..000000000 --- a/src/pages/golang/vs-java.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'go-vs-java'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/guides/[guideId].astro b/src/pages/guides/[guideId].astro deleted file mode 100644 index c6a7f28c7..000000000 --- a/src/pages/guides/[guideId].astro +++ /dev/null @@ -1,47 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getAllGuides, type GuideFileType } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; -import { replaceVariables } from '../../lib/markdown'; - -export const prerender = true; - -export interface Props { - guide: GuideFileType; -} - -export async function getStaticPaths() { - const guides = (await getAllGuides()).filter( - (guide) => !guide.frontmatter.excludedBySlug, - ); - - return guides.map((guide) => ({ - params: { guideId: guide.id }, - props: { guide }, - })); -} - -const { guideId } = Astro.params; -const { guide } = Astro.props; -const { frontmatter: guideData, author } = guide; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - - -
        - diff --git a/src/pages/guides/index.astro b/src/pages/guides/index.astro deleted file mode 100644 index ac7eeb02e..000000000 --- a/src/pages/guides/index.astro +++ /dev/null @@ -1,40 +0,0 @@ ---- -import { GuideListItem } from '../../components/FeaturedGuides/GuideListItem'; -import SimplePageHeader from '../../components/SimplePageHeader.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getAllGuides } from '../../lib/guide'; -import { getAllQuestionGroups } from '../../lib/question-group'; - -const guides = await getAllGuides(); -const questionGuides = (await getAllQuestionGroups()).filter( - (questionGroup) => questionGroup.frontmatter.authorId, -); - -const allGuides = [...guides, ...questionGuides]; -const sortedGuides = allGuides.sort((a, b) => { - const aDate = new Date(a.frontmatter.date as string); - const bDate = new Date(b.frontmatter.date as string); - - return bDate.getTime() - aDate.getTime(); -}); ---- - - - - -
        -
        -
        - {sortedGuides.map((guide) => )} -
        -
        -
        -
        -
        diff --git a/src/pages/index.astro b/src/pages/index.astro index 161aa2178..252ad59de 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -6,15 +6,13 @@ import { FeaturedVideoList } from '../components/FeaturedVideos/FeaturedVideoLis import HeroSection from '../components/HeroSection/HeroSection.astro'; import BaseLayout from '../layouts/BaseLayout.astro'; import { getAllBestPractices } from '../lib/best-practice'; -import { getAllGuides } from '../lib/guide'; -import { getAllQuestionGroups } from '../lib/question-group'; import { getRoadmapsByTag } from '../lib/roadmap'; import { getAllVideos } from '../lib/video'; +import { listOfficialGuides } from '../queries/official-guide'; const roleRoadmaps = await getRoadmapsByTag('role-roadmap'); const skillRoadmaps = await getRoadmapsByTag('skill-roadmap'); const bestPractices = await getAllBestPractices(); -const questionGroups = await getAllQuestionGroups(); export const projectGroups = [ { @@ -31,9 +29,9 @@ export const projectGroups = [ }, ]; -const guides = await getAllGuides(); -const questionGuides = (await getAllQuestionGroups()).filter( - (questionGroup) => questionGroup.frontmatter.authorId, +const guides = await listOfficialGuides(); +const questionGuides = guides.filter( + (guide) => guide.roadmapId === 'questions' && !!guide?.authorId, ); const videos = await getAllVideos(); --- @@ -97,16 +95,6 @@ const videos = await getAllVideos(); }))} /> - ({ - text: questionGroup.frontmatter.briefTitle, - url: `/questions/${questionGroup.id}`, - isNew: questionGroup.frontmatter.isNew, - }))} - /> -
        - -
        - diff --git a/src/pages/java/vs-javascript.astro b/src/pages/java/vs-javascript.astro deleted file mode 100644 index 46343ae44..000000000 --- a/src/pages/java/vs-javascript.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'java-vs-javascript'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/java/vs-python.astro b/src/pages/java/vs-python.astro deleted file mode 100644 index 7f8632aa5..000000000 --- a/src/pages/java/vs-python.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'java-vs-python'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        - diff --git a/src/pages/javascript/vs-typescript.astro b/src/pages/javascript/vs-typescript.astro deleted file mode 100644 index 46351390a..000000000 --- a/src/pages/javascript/vs-typescript.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'javascript-vs-typescript'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        diff --git a/src/pages/og/guide/[slug].ts b/src/pages/og/guide/[slug].ts index 947d023d1..ee35a7ba0 100644 --- a/src/pages/og/guide/[slug].ts +++ b/src/pages/og/guide/[slug].ts @@ -3,7 +3,7 @@ import { getDefaultOpenGraphImageBuffer, getResourceOpenGraph, } from '../../../lib/open-graph'; -import {Transformer} from "@napi-rs/image"; +import { Transformer } from '@napi-rs/image'; export const prerender = false; @@ -13,6 +13,7 @@ type Params = { export const GET: APIRoute = async (context) => { const { slug } = context.params; + const { searchParams } = context.url; if (!slug) { const buffer = await getDefaultOpenGraphImageBuffer(); @@ -23,9 +24,14 @@ export const GET: APIRoute = async (context) => { }); } - const svg = await getResourceOpenGraph('guide', slug); + const svg = await getResourceOpenGraph( + 'guide', + slug, + Object.fromEntries(searchParams.entries()), + ); const transformer = Transformer.fromSvg(svg).crop(0, 0, 1200, 630); + // @ts-expect-error - Buffer is not assignable to BodyInit return new Response(await transformer.png(), { headers: { 'Content-Type': 'image/png', diff --git a/src/pages/sql/hard-to-learn.astro b/src/pages/sql/hard-to-learn.astro deleted file mode 100644 index 75fdeab5d..000000000 --- a/src/pages/sql/hard-to-learn.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'sql-hard-to-learn'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        \ No newline at end of file diff --git a/src/pages/sql/how-long-to-learn.astro b/src/pages/sql/how-long-to-learn.astro deleted file mode 100644 index bcae6a815..000000000 --- a/src/pages/sql/how-long-to-learn.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'sql-how-long-to-learn'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        \ No newline at end of file diff --git a/src/pages/sql/vs-mysql.astro b/src/pages/sql/vs-mysql.astro deleted file mode 100644 index 8076683e8..000000000 --- a/src/pages/sql/vs-mysql.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'sql-vs-mysql'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        \ No newline at end of file diff --git a/src/pages/sql/vs-python.astro b/src/pages/sql/vs-python.astro deleted file mode 100644 index cec52ad78..000000000 --- a/src/pages/sql/vs-python.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -import GuideContent from '../../components/Guide/GuideContent.astro'; -import BaseLayout from '../../layouts/BaseLayout.astro'; -import { getGuideById } from '../../lib/guide'; -import { getOpenGraphImageUrl } from '../../lib/open-graph'; - -const guideId = 'sql-vs-python'; -const guide = await getGuideById(guideId); - -const { frontmatter: guideData } = guide!; - -const ogImageUrl = - guideData.seo.ogImageUrl || - getOpenGraphImageUrl({ - group: 'guide', - resourceId: guideId, - }); ---- - - - -
        -
        \ No newline at end of file diff --git a/src/queries/official-guide.ts b/src/queries/official-guide.ts new file mode 100644 index 000000000..c8215845b --- /dev/null +++ b/src/queries/official-guide.ts @@ -0,0 +1,113 @@ +import { FetchError, httpGet } from '../lib/query-http'; + +export const allowedOfficialGuideStatus = ['draft', 'published'] as const; +export type AllowedOfficialGuideStatus = + (typeof allowedOfficialGuideStatus)[number]; + +export interface OfficialGuideDocument { + _id: string; + title: string; + slug: string; + description?: string; + content: any; + authorId: string; + roadmapId?: string; + featuredImage?: string; + status: AllowedOfficialGuideStatus; + publishedAt?: Date; + seo?: { + metaTitle?: string; + metaDescription?: string; + keywords?: string[]; + }; + tags?: string[]; + + questionCount?: number; + questionTopicCount?: number; + + viewCount?: number; + createdAt: Date; + updatedAt: Date; +} + +type ListOfficialGuidesQuery = { + authorSlug?: string; + roadmapId?: string; +}; + +export async function listOfficialGuides(query: ListOfficialGuidesQuery = {}) { + try { + const guides = await httpGet( + `/v1-list-official-guides`, + query, + ); + + return guides.sort((a, b) => { + const aDate = new Date(a.createdAt); + const bDate = new Date(b.createdAt); + + return bDate.getTime() - aDate.getTime(); + }); + } catch (error) { + if (FetchError.isFetchError(error) && error.status === 404) { + return []; + } + + throw error; + } +} + +export interface OfficialAuthorDocument { + _id: string; + name: string; + slug: string; + bio?: string; + avatar?: string; + socialLinks?: { + twitter?: string; + linkedin?: string; + github?: string; + website?: string; + }; + isActive: boolean; + createdAt: Date; + updatedAt: Date; +} + +type GuideWithAuthor = OfficialGuideDocument & { + author?: Pick< + OfficialAuthorDocument, + 'name' | 'slug' | 'avatar' | 'bio' | 'socialLinks' + >; + relatedGuides?: Pick[]; +}; + +export type OfficialGuideResponse = GuideWithAuthor; + +export async function getOfficialGuide(slug: string, roadmapId?: string) { + try { + const guide = await httpGet( + `/v1-official-guide/${slug}`, + { + ...(roadmapId ? { roadmapId } : {}), + }, + ); + + return guide; + } catch (error) { + if (FetchError.isFetchError(error) && error.status === 404) { + return null; + } + + throw error; + } +} + +export function getOfficialGuideHref(slug: string, roadmapId?: string) { + const isExternal = roadmapId && roadmapId !== 'questions'; + return isExternal + ? `${import.meta.env.PUBLIC_APP_URL}/${roadmapId}/${slug}` + : roadmapId + ? `/${roadmapId}/${slug}` + : `/guides/${slug}`; +}