Skip to content

Commit 4a8cf87

Browse files
author
afterrburn
committed
updating tutorial api reader
1 parent 174676c commit 4a8cf87

File tree

6 files changed

+139
-38
lines changed

6 files changed

+139
-38
lines changed

app/api/tutorials/[id]/route.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { NextRequest, NextResponse } from 'next/server';
2-
import { join } from 'path';
32
import { parseTutorialMDXCached } from '@/lib/tutorial/mdx-parser';
43
import { TutorialIdParamsSchema } from '@/lib/tutorial/schemas';
4+
import { getTutorialFilePath } from '@/lib/tutorial';
55

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

2323
const { id } = validationResult.data;
24-
const filePath = join(process.cwd(), 'content', 'Tutorial', `${id}.mdx`);
24+
const filePath = await getTutorialFilePath(id);
25+
if (!filePath) {
26+
return NextResponse.json(
27+
{ success: false, error: 'Tutorial not found' },
28+
{ status: 404 }
29+
);
30+
}
2531

2632
const parsed = await parseTutorialMDXCached(filePath);
2733

app/api/tutorials/[id]/steps/[stepNumber]/route.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { NextRequest, NextResponse } from 'next/server';
2-
import { join } from 'path';
32
import { parseTutorialMDXCached } from '@/lib/tutorial/mdx-parser';
43
import { StepParamsSchema } from '@/lib/tutorial/schemas';
4+
import { getTutorialFilePath } from '@/lib/tutorial';
55

66
interface RouteParams {
77
params: Promise<{ id: string; stepNumber: string }>;
@@ -10,29 +10,34 @@ interface RouteParams {
1010
export async function GET(request: NextRequest, { params }: RouteParams) {
1111
try {
1212
const rawParams = await params;
13-
14-
// Validate and transform parameters with Zod
13+
1514
const validationResult = StepParamsSchema.safeParse(rawParams);
1615
if (!validationResult.success) {
1716
return NextResponse.json(
1817
{ success: false, error: 'Invalid parameters', details: validationResult.error.format() },
1918
{ status: 400 }
2019
);
2120
}
22-
21+
2322
const { id, stepNumber: stepNum } = validationResult.data;
24-
25-
const filePath = join(process.cwd(), 'content', 'Tutorial', `${id}.mdx`);
23+
24+
const filePath = await getTutorialFilePath(id);
25+
if (!filePath) {
26+
return NextResponse.json(
27+
{ success: false, error: 'Tutorial not found' },
28+
{ status: 404 }
29+
);
30+
}
2631
const parsed = await parseTutorialMDXCached(filePath);
27-
32+
2833
const step = parsed.steps.find(s => s.stepNumber === stepNum);
2934
if (!step) {
3035
return NextResponse.json(
3136
{ success: false, error: 'Step not found' },
3237
{ status: 404 }
3338
);
3439
}
35-
40+
3641
return NextResponse.json({
3742
success: true,
3843
data: {

app/api/tutorials/route.ts

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,46 @@
11
import { NextResponse } from 'next/server';
2-
import { readdir } from 'fs/promises';
32
import { join } from 'path';
43
import { parseTutorialMDXCached } from '@/lib/tutorial/mdx-parser';
54
import { TutorialListItemSchema, type TutorialListItem } from '@/lib/tutorial/schemas';
5+
import { getTutorialsConfig } from '@/lib/tutorial';
66

77
export async function GET() {
88
try {
9-
const tutorialsDir = join(process.cwd(), 'content', 'Tutorial');
10-
11-
// Check if Tutorial directory exists, if not create it for future use
12-
let entries: string[];
13-
try {
14-
entries = await readdir(tutorialsDir);
15-
} catch (error) {
16-
// Return empty array if Tutorial directory doesn't exist yet
17-
return NextResponse.json([]);
18-
}
19-
20-
const mdxFiles = entries.filter(file => file.endsWith('.mdx'));
21-
9+
const config = await getTutorialsConfig();
10+
2211
const tutorials = await Promise.all(
23-
mdxFiles.map(async (file): Promise<TutorialListItem | null> => {
12+
config.tutorials.map(async (tutorialMeta): Promise<TutorialListItem | null> => {
2413
try {
25-
const filePath = join(tutorialsDir, file);
14+
const filePath = join(process.cwd(), 'content', tutorialMeta.path);
2615
const parsed = await parseTutorialMDXCached(filePath);
27-
16+
2817
const tutorialItem = {
29-
id: file.replace('.mdx', ''),
30-
title: parsed.metadata.title,
31-
description: parsed.metadata.description,
32-
totalSteps: parsed.metadata.totalSteps,
33-
difficulty: parsed.metadata.difficulty,
34-
estimatedTime: parsed.metadata.estimatedTime,
18+
id: tutorialMeta.id,
19+
title: tutorialMeta.title,
20+
description: tutorialMeta.description,
21+
totalSteps: parsed.metadata.totalSteps || parsed.steps.length,
22+
difficulty: tutorialMeta.difficulty,
23+
estimatedTime: tutorialMeta.estimatedTime,
3524
};
3625

3726
// Validate the tutorial list item
3827
const validationResult = TutorialListItemSchema.safeParse(tutorialItem);
3928
if (!validationResult.success) {
40-
console.warn(`Invalid tutorial item ${file}:`, validationResult.error.message);
29+
console.warn(`Invalid tutorial item ${tutorialMeta.id}:`, validationResult.error.message);
4130
return null;
4231
}
4332

4433
return validationResult.data;
4534
} catch (error) {
46-
console.warn(`Failed to parse tutorial ${file}:`, error);
35+
console.warn(`Failed to parse tutorial ${tutorialMeta.id} at ${tutorialMeta.path}:`, error);
4736
return null;
4837
}
4938
})
5039
);
51-
40+
5241
// Filter out failed tutorials
5342
const validTutorials = tutorials.filter(tutorial => tutorial !== null);
54-
43+
5544
return NextResponse.json(validTutorials);
5645
} catch (error) {
5746
console.error('Failed to load tutorials:', error);

content/tutorials.json

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"_comment": "This file defines all available tutorials for the tutorial API endpoints. The tutorial API (/api/tutorials) reads from this file to determine which MDX files to process as tutorials, ensuring only intentionally marked files are included. Each tutorial entry must have a corresponding MDX file at the specified path.",
3+
"tutorials": [
4+
{
5+
"id": "01-introduction-to-agents",
6+
"title": "Module 1: Introduction to Agents",
7+
"description": "Understanding AI agents and the $47B opportunity",
8+
"path": "Training/developers/01-introduction-to-agents.mdx",
9+
"difficulty": "beginner",
10+
"estimatedTime": "15 minutes"
11+
},
12+
{
13+
"id": "02-anatomy-of-an-agent",
14+
"title": "Module 2: The Anatomy of an Agent",
15+
"description": "Understanding how agents work - planning, reasoning, tools, and memory",
16+
"path": "Training/developers/02-anatomy-of-an-agent.mdx",
17+
"difficulty": "beginner",
18+
"estimatedTime": "20 minutes"
19+
},
20+
{
21+
"id": "03-agent-memory",
22+
"title": "Module 3: Agent Memory",
23+
"description": "How agents remember, learn, and build context over time",
24+
"path": "Training/developers/03-agent-memory.mdx",
25+
"difficulty": "beginner",
26+
"estimatedTime": "55 minutes"
27+
},
28+
{
29+
"id": "04-agent-collaboration",
30+
"title": "Module 4: Agent-to-Agent Collaboration",
31+
"description": "Building multi-agent systems that work together",
32+
"path": "Training/developers/04-agent-collaboration.mdx",
33+
"difficulty": "intermediate",
34+
"estimatedTime": "30 minutes"
35+
},
36+
{
37+
"id": "05-observability-guardrails-evals",
38+
"title": "Module 5: Observability, Guardrails, & Evals",
39+
"description": "Making agents reliable, safe, and production-ready",
40+
"path": "Training/developers/05-observability-guardrails-evals.mdx",
41+
"difficulty": "intermediate",
42+
"estimatedTime": "25 minutes"
43+
},
44+
{
45+
"id": "06-deployment-environments",
46+
"title": "Module 6: Deployment Environments",
47+
"description": "From local development to global production",
48+
"path": "Training/developers/06-deployment-environments.mdx",
49+
"difficulty": "intermediate",
50+
"estimatedTime": "20 minutes"
51+
},
52+
{
53+
"id": "07-sandbox-capstone",
54+
"title": "Module 7: Advanced Multi-Agent Research System",
55+
"description": "Building sophisticated agent coordination with recursive research",
56+
"path": "Training/developers/07-sandbox-capstone.mdx",
57+
"difficulty": "advanced",
58+
"estimatedTime": "45 minutes"
59+
}
60+
]
61+
}

lib/tutorial/index.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,43 @@
11
// Tutorial state management exports
22
export * from './types';
33
export * from './state-manager';
4+
5+
// Tutorial configuration utilities
6+
import { join } from 'path';
7+
import { readFile } from 'fs/promises';
8+
9+
interface TutorialMeta {
10+
id: string;
11+
title: string;
12+
description: string;
13+
path: string;
14+
difficulty?: string;
15+
estimatedTime?: string;
16+
}
17+
18+
interface TutorialsConfig {
19+
tutorials: TutorialMeta[];
20+
}
21+
22+
/**
23+
* Reads the tutorials.json configuration file
24+
*/
25+
export async function getTutorialsConfig(): Promise<TutorialsConfig> {
26+
const configPath = join(process.cwd(), 'content', 'tutorials.json');
27+
const configContent = await readFile(configPath, 'utf-8');
28+
return JSON.parse(configContent);
29+
}
30+
31+
/**
32+
* Gets the full file path for a specific tutorial based on tutorials.json
33+
*/
34+
export async function getTutorialFilePath(tutorialId: string): Promise<string | null> {
35+
const config = await getTutorialsConfig();
36+
const tutorial = config.tutorials.find(t => t.id === tutorialId);
37+
38+
if (!tutorial) {
39+
return null;
40+
}
41+
42+
return join(process.cwd(), 'content', tutorial.path);
43+
}

lib/tutorial/schemas.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { z } from 'zod';
44
export const TutorialMetadataSchema = z.object({
55
title: z.string().min(1, 'Title is required'),
66
description: z.string().min(1, 'Description is required'),
7-
totalSteps: z.number().positive('Total steps must be a positive number'),
7+
totalSteps: z.number().positive('Total steps must be a positive number').optional(),
88
difficulty: z.enum(['beginner', 'intermediate', 'advanced']).optional(),
99
estimatedTime: z.string().optional()
1010
});

0 commit comments

Comments
 (0)