Skip to content
This repository has been archived by the owner on Nov 20, 2024. It is now read-only.

Dev firgrep #30

Merged
merged 6 commits into from
Sep 25, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
dynamic mdx fetch and compile. initial test OK
Firgrep committed Sep 21, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit 59a845f297fb628cb1cd48a3f61dccc9aaebda47
51 changes: 51 additions & 0 deletions src/app/test/[courseSlug]/[lessonSlug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import LoadingBars from "@/components/LoadingBars";
import MDXRenderer from "@/components/MDXRenderer";
import { MdxGetCompiledSourceProps, mdxGetCompiledSource } from "@/server/controllers/mdxController";
import { Suspense } from "react";


/**
* * TEST ROUTE
* * /test/first-course-updated/logic-introduction
*/
export default async function TestPage2({ params }: { params: { courseSlug: string, lessonSlug: string}}) {
const courseSlug = params.courseSlug;
const lessonSlug = params.lessonSlug;
/**
* TODO Why must I add the Props here for TS not to yell at me!?
*/
const mdxGetArgs: MdxGetCompiledSourceProps = {
courseSlug: courseSlug,
lessonSlug: lessonSlug,
lessonType: "CONTENT",
access: "PUBLIC",
}
const compiledMdx = await mdxGetCompiledSource(mdxGetArgs)

const mdxGetArgs2: MdxGetCompiledSourceProps = {
courseSlug: courseSlug,
lessonSlug: lessonSlug,
lessonType: "TRANSCRIPT",
access: "PUBLIC",
}
const compiledMdx2 = await mdxGetCompiledSource(mdxGetArgs2)

return (
<main className="h-screen flex flex-col justify-front items-center gap-4 bg-slate-200">
<p>Test page with 2 dynamic retrieval from db</p>
<div className="container">
<p>Content:</p>
<Suspense fallback={<LoadingBars />}>
<MDXRenderer data={compiledMdx} />
</Suspense>

<p>Transcript</p>
<Suspense fallback={<LoadingBars />}>
<MDXRenderer data={compiledMdx2} />
</Suspense>
</div>


</main>
)
}
34 changes: 34 additions & 0 deletions src/app/test/[courseSlug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import LoadingBars from "@/components/LoadingBars";
import MDXRenderer from "@/components/MDXRenderer";
import { MdxGetCompiledSourceProps, mdxGetCompiledSource } from "@/server/controllers/mdxController";
import { Suspense } from "react";


/**
* * TEST ROUTE
* * /test/first-course-updated
*/
export default async function TestPage1({ params }: { params: { courseSlug: string }}) {
const courseSlug = params.courseSlug;
/**
* TODO Why must I add the Props here for TS not to yell at me!?
*/
const mdxGetArgs: MdxGetCompiledSourceProps = {
courseSlug: courseSlug,
access: "PUBLIC",
}
const compiledMdx = await mdxGetCompiledSource(mdxGetArgs)

return (
<main className="h-screen flex flex-col justify-front items-center gap-4 bg-slate-200">
<p>Test page with 1 dynamic retrieval from db</p>
<div className="container">
<Suspense fallback={<LoadingBars />}>
<MDXRenderer data={compiledMdx} />
</Suspense>
</div>


</main>
)
}
1 change: 1 addition & 0 deletions src/server/auth.ts
Original file line number Diff line number Diff line change
@@ -42,6 +42,7 @@ export const authOptions: NextAuthOptions = {
],
};

export type Access = "PUBLIC" | "USER" | "ADMIN";
/**
* Helper function to get Auth Session on serverside.
*/
90 changes: 79 additions & 11 deletions src/server/controllers/coursesController.ts
Original file line number Diff line number Diff line change
@@ -136,7 +136,7 @@ export const dbGetLessonAndRelationsById = async (id: string) => {
* Converts binary content of found record to string so that it can pass the tRPC network boundary
* and/or be passed down to Client Components from Server Components.
* @supports LessonContent | LessonTranscript | CourseDetails
* @access "ADMIN" | "INTERNAL"
* @access "ADMIN" | "INTERNAL" (add `true` as second argument to bypass Auth check)
*/
export const dbGetMdxContentByModelId = async (id: string, internal?: boolean) => {
try {
@@ -161,7 +161,7 @@ export const dbGetMdxContentByModelId = async (id: string, internal?: boolean) =
where: {
id: validId,
}
})
});
if (!lessonTranscript) {
/**
* If no matching id found, proceed to query CourseDetails.
@@ -170,7 +170,7 @@ export const dbGetMdxContentByModelId = async (id: string, internal?: boolean) =
where: {
id: validId,
}
})
});
/**
* All three query attempts failed, throw error.
*/
@@ -214,19 +214,87 @@ export const dbGetMdxContentByModelId = async (id: string, internal?: boolean) =
export type DBGetMdxContentByModelIdReturnType = Awaited<ReturnType<typeof dbGetMdxContentByModelId>>;

export type LessonTypes = "CONTENT" | "TRANSCRIPT";
export type DBGetMdxContentBySlugsProps = {
courseSlug: string;
} & (
{
lessonSlug: string,
lessonType: LessonTypes;
} | {
lessonSlug?: never;
lessonType?: never;
}
)
/**
* TODO - WIP
* Get uncompiled MDX by Course slug and/or Lesson slug. If only Course slug is provided, the
* function will attempt to find and retrieve the MDX of the CourseDetails that is
* related to this course. To get the MDX pertaining to a Lesson, a lessonType must
* be specified.
* @returns object with uncompiled MDX || placeholder string if data model non-existent
*/
export const dbGetMdxContentBySlugs = ({
export const dbGetMdxContentBySlugs = async ({
courseSlug,
lessonSlug,
lessonType,
}: {
courseSlug: string;
lessonSlug?: string;
lessonType?: LessonTypes
}) => {

}: DBGetMdxContentBySlugsProps) => {
const validCourseSlug = z.string().parse(courseSlug);
const validLessonSlug = z.string().optional().parse(lessonSlug);
/**
* Since a Course may exist without CourseDetails, and Lesson may exist
* without LessonContent and LessonTranscript, instead of throwing an error
* a string is returned when the respective MDX data models are non-existant.
*/
if (validCourseSlug && validLessonSlug && lessonType) {
if (lessonType === "CONTENT") {
const lessonContent = await prisma.lessonContent.findFirst({
where: {
lesson: {
slug: validLessonSlug,
course: {
slug: validCourseSlug,
}
}
},
select: {
id: true,
}
})
if (!lessonContent) return "No lesson content";
return dbGetMdxContentByModelId(lessonContent.id, true);
}
if (lessonType === "TRANSCRIPT") {
const lessonTranscript = await prisma.lessonTranscript.findFirst({
where: {
lesson: {
slug: validLessonSlug,
course: {
slug: validCourseSlug,
}
}
},
select: {
id: true,
}
})
if (!lessonTranscript) return "No lesson transcript";
return dbGetMdxContentByModelId(lessonTranscript.id, true);
}
}
if (validCourseSlug) {
const courseDetails = await prisma.courseDetails.findFirst({
where: {
course: {
slug: validCourseSlug,
}
},
select: {
id: true,
}
});
if (!courseDetails) return "No course details";
return dbGetMdxContentByModelId(courseDetails.id, true);
}
throw new Error("Error occured when attempting to find data models by slug(s)")
}

/**
53 changes: 40 additions & 13 deletions src/server/controllers/mdxController.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { AuthenticationError, requireAdminAuth } from "../auth";
import { type LessonTypes } from "./coursesController";
import { z } from "zod";
import { type Access, AuthenticationError, requireAdminAuth } from "../auth";
import { dbGetMdxContentBySlugs, DBGetMdxContentBySlugsProps, type LessonTypes } from "./coursesController";
import { mdxCompiler } from "../mdxCompiler";
import { filterMarkdownAndType } from "@/utils/utils";

type MdxGetCompiledSourceProps = {
courseSlug: string;

export type MdxGetCompiledSourceProps = {
partSlug?: string;
lessonSlug?: string;
lessonType?: LessonTypes;
access: "PUBLIC" | "USER" | "ADMIN";
}
access: Access;
} & DBGetMdxContentBySlugsProps;
/**
* TODO - WIP
* Get compiled, render-ready MDX by Course slug and/or Lesson slug identifiers.
* If only Course slug is provided, it will attempt to retrieve MDX from CourseDetails,
* otherwise lesson type must be added along with Lesson slug to retrieve MDX from
* either LessonContent or LessonTranscript. If records are nonexistent for any of these
* MDX models, then a placeholder string is returned.
* @access Access must be specified.
*/
export const mdxGetCompiledSource = async ({
courseSlug, partSlug, lessonSlug, lessonType, access
@@ -23,11 +29,32 @@ export const mdxGetCompiledSource = async ({
} else if (access === "USER") {
// TODO perform "USER" checks here
}


return;
/**
* Validation and retrieval from db
*/
const dbGetArgs = lessonSlug && lessonType
?
{
courseSlug: z.string().parse(courseSlug),
lessonSlug: z.string().parse(lessonSlug),
lessonType: lessonType,
}
:
{
courseSlug: z.string().parse(courseSlug),
}
const uncompiledMdxContainer = await dbGetMdxContentBySlugs(dbGetArgs);
/**
* If records are non-existent in db, placeholder strings will be returned.
* Otherwise, an object of one of many possible models is returned,
* which needs to be filtered for the MDX string before it is compiled.
*/
if (typeof uncompiledMdxContainer === "string") {
return await mdxCompiler(uncompiledMdxContainer);
}
const [uncompiledMdx] = filterMarkdownAndType(uncompiledMdxContainer);
return await mdxCompiler(uncompiledMdx);
} catch (error) {

if (error instanceof AuthenticationError) {
throw error; // Rethrow custom error as-is
}