Skip to content
Draft
Show file tree
Hide file tree
Changes from 7 commits
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
10 changes: 8 additions & 2 deletions app/api/tutorials/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { join } from 'path';
import { parseTutorialMDXCached } from '@/lib/tutorial/mdx-parser';
import { TutorialIdParamsSchema } from '@/lib/tutorial/schemas';
import { getTutorialFilePath } from '@/lib/tutorial';

interface RouteParams {
params: Promise<{ id: string }>;
Expand All @@ -21,7 +21,13 @@ export async function GET(request: NextRequest, { params }: RouteParams) {
}

const { id } = validationResult.data;
const filePath = join(process.cwd(), 'content', 'Tutorial', `${id}.mdx`);
const filePath = await getTutorialFilePath(id);
if (!filePath) {
return NextResponse.json(
{ success: false, error: 'Tutorial not found' },
{ status: 404 }
);
}

const parsed = await parseTutorialMDXCached(filePath);

Expand Down
21 changes: 13 additions & 8 deletions app/api/tutorials/[id]/steps/[stepNumber]/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { join } from 'path';
import { parseTutorialMDXCached } from '@/lib/tutorial/mdx-parser';
import { StepParamsSchema } from '@/lib/tutorial/schemas';
import { getTutorialFilePath } from '@/lib/tutorial';

interface RouteParams {
params: Promise<{ id: string; stepNumber: string }>;
Expand All @@ -10,29 +10,34 @@ interface RouteParams {
export async function GET(request: NextRequest, { params }: RouteParams) {
try {
const rawParams = await params;

// Validate and transform parameters with Zod

const validationResult = StepParamsSchema.safeParse(rawParams);
if (!validationResult.success) {
return NextResponse.json(
{ success: false, error: 'Invalid parameters', details: validationResult.error.format() },
{ status: 400 }
);
}

const { id, stepNumber: stepNum } = validationResult.data;

const filePath = join(process.cwd(), 'content', 'Tutorial', `${id}.mdx`);

const filePath = await getTutorialFilePath(id);
if (!filePath) {
return NextResponse.json(
{ success: false, error: 'Tutorial not found' },
{ status: 404 }
);
}
const parsed = await parseTutorialMDXCached(filePath);

const step = parsed.steps.find(s => s.stepNumber === stepNum);
if (!step) {
return NextResponse.json(
{ success: false, error: 'Step not found' },
{ status: 404 }
);
}

return NextResponse.json({
success: true,
data: {
Expand Down
43 changes: 16 additions & 27 deletions app/api/tutorials/route.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,46 @@
import { NextResponse } from 'next/server';
import { readdir } from 'fs/promises';
import { join } from 'path';
import { parseTutorialMDXCached } from '@/lib/tutorial/mdx-parser';
import { TutorialListItemSchema, type TutorialListItem } from '@/lib/tutorial/schemas';
import { getTutorialsConfig } from '@/lib/tutorial';

export async function GET() {
try {
const tutorialsDir = join(process.cwd(), 'content', 'Tutorial');

// Check if Tutorial directory exists, if not create it for future use
let entries: string[];
try {
entries = await readdir(tutorialsDir);
} catch (error) {
// Return empty array if Tutorial directory doesn't exist yet
return NextResponse.json([]);
}

const mdxFiles = entries.filter(file => file.endsWith('.mdx'));

const config = await getTutorialsConfig();

const tutorials = await Promise.all(
mdxFiles.map(async (file): Promise<TutorialListItem | null> => {
config.tutorials.map(async (tutorialMeta): Promise<TutorialListItem | null> => {
try {
const filePath = join(tutorialsDir, file);
const filePath = join(process.cwd(), 'content', tutorialMeta.path);
const parsed = await parseTutorialMDXCached(filePath);

const tutorialItem = {
id: file.replace('.mdx', ''),
title: parsed.metadata.title,
description: parsed.metadata.description,
totalSteps: parsed.metadata.totalSteps,
difficulty: parsed.metadata.difficulty,
estimatedTime: parsed.metadata.estimatedTime,
id: tutorialMeta.id,
title: tutorialMeta.title,
description: tutorialMeta.description,
totalSteps: parsed.metadata.totalSteps || parsed.steps.length,
difficulty: tutorialMeta.difficulty,
estimatedTime: tutorialMeta.estimatedTime,
};

// Validate the tutorial list item
const validationResult = TutorialListItemSchema.safeParse(tutorialItem);
if (!validationResult.success) {
console.warn(`Invalid tutorial item ${file}:`, validationResult.error.message);
console.warn(`Invalid tutorial item ${tutorialMeta.id}:`, validationResult.error.message);
return null;
}

return validationResult.data;
} catch (error) {
console.warn(`Failed to parse tutorial ${file}:`, error);
console.warn(`Failed to parse tutorial ${tutorialMeta.id} at ${tutorialMeta.path}:`, error);
return null;
}
})
);

// Filter out failed tutorials
const validTutorials = tutorials.filter(tutorial => tutorial !== null);

return NextResponse.json(validTutorials);
} catch (error) {
console.error('Failed to load tutorials:', error);
Expand Down
Loading