route.ts 1.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
  1. import { readFile } from "fs/promises";
  2. import { join } from "path";
  3. import { NextRequest, NextResponse } from "next/server";
  4. import { mdxToCleanMarkdown } from "@/lib/mdx-to-markdown";
  5. export async function GET(req: NextRequest) {
  6. const { searchParams } = new URL(req.url);
  7. const docPath = searchParams.get("path");
  8. if (!docPath) {
  9. return NextResponse.json(
  10. { error: "Missing ?path= parameter" },
  11. { status: 400 },
  12. );
  13. }
  14. // Sanitize path: only allow docs paths, no traversal
  15. const normalized = docPath
  16. .replace(/^\//, "")
  17. .replace(/\.\./g, "")
  18. .replace(/[^a-zA-Z0-9/-]/g, "");
  19. if (!normalized.startsWith("docs")) {
  20. return NextResponse.json({ error: "Invalid path" }, { status: 400 });
  21. }
  22. // Map URL path to file path
  23. // /docs -> /app/(main)/docs/page.mdx
  24. // /docs/installation -> /app/(main)/docs/installation/page.mdx
  25. const slug = normalized === "docs" ? "" : normalized.replace(/^docs\/?/, "");
  26. const filePath = slug
  27. ? join(
  28. process.cwd(),
  29. "app",
  30. "(main)",
  31. "docs",
  32. ...slug.split("/"),
  33. "page.mdx",
  34. )
  35. : join(process.cwd(), "app", "(main)", "docs", "page.mdx");
  36. try {
  37. const raw = await readFile(filePath, "utf-8");
  38. const markdown = mdxToCleanMarkdown(raw);
  39. return new NextResponse(markdown, {
  40. headers: {
  41. "Content-Type": "text/markdown; charset=utf-8",
  42. "Cache-Control": "public, max-age=3600",
  43. },
  44. });
  45. } catch {
  46. return NextResponse.json({ error: "Page not found" }, { status: 404 });
  47. }
  48. }