| 
1 |  | -import { NextRequest, NextResponse } from 'next/server'  | 
2 |  | -import fs from 'fs'  | 
 | 1 | +import { NextResponse } from 'next/server'  | 
 | 2 | +import fs from 'fs/promises'  | 
3 | 3 | import path from 'path'  | 
4 |  | -import { URL } from 'url'  | 
5 | 4 | 
 
  | 
6 |  | -const STORAGE_DIR = path.join(process.cwd(), 'storage', 'markdown')  | 
 | 5 | +const STORAGE_DIR = path.join(process.cwd(), 'storage/markdown')  | 
7 | 6 | 
 
  | 
8 |  | -// Ensure storage directory exists  | 
9 |  | -if (!fs.existsSync(STORAGE_DIR)) {  | 
10 |  | -  fs.mkdirSync(STORAGE_DIR, { recursive: true })  | 
11 |  | -}  | 
12 |  | - | 
13 |  | -function getFilenameFromUrl(url: string): string {  | 
14 |  | -  try {  | 
15 |  | -    const parsedUrl = new URL(url)  | 
16 |  | -    // Use hostname and pathname to create a unique filename  | 
17 |  | -    const filename = `${parsedUrl.hostname}${parsedUrl.pathname.replace(/\//g, '_')}`  | 
18 |  | -      .replace(/[^a-zA-Z0-9-_]/g, '_') // Replace invalid chars with underscore  | 
19 |  | -      .replace(/_+/g, '_') // Replace multiple underscores with single  | 
20 |  | -      .toLowerCase()  | 
21 |  | -    return `${filename}.json`  | 
22 |  | -  } catch (error) {  | 
23 |  | -    // Fallback for invalid URLs  | 
24 |  | -    return `${url.replace(/[^a-zA-Z0-9-_]/g, '_')}.json`  | 
25 |  | -  }  | 
26 |  | -}  | 
27 |  | - | 
28 |  | -// POST /api/storage - Save markdown  | 
29 |  | -export async function POST(request: NextRequest) {  | 
 | 7 | +export async function POST(request: Request) {  | 
30 | 8 |   try {  | 
31 |  | -    const { url, content, stats } = await request.json()  | 
32 |  | -    const filename = getFilenameFromUrl(url)  | 
33 |  | -    const filepath = path.join(STORAGE_DIR, filename)  | 
34 |  | - | 
35 |  | -    const data = {  | 
36 |  | -      url,  | 
37 |  | -      content,  | 
38 |  | -      timestamp: new Date().toISOString(),  | 
39 |  | -      stats  | 
40 |  | -    }  | 
41 |  | - | 
42 |  | -    // Save JSON file  | 
43 |  | -    fs.writeFileSync(filepath, JSON.stringify(data, null, 2))  | 
 | 9 | +    const { url, content } = await request.json()  | 
 | 10 | +      | 
 | 11 | +    // Create storage directory if it doesn't exist  | 
 | 12 | +    await fs.mkdir(STORAGE_DIR, { recursive: true })  | 
 | 13 | +      | 
 | 14 | +    // Generate filename from URL  | 
 | 15 | +    const filename = url  | 
 | 16 | +      .replace(/^https?:\/\//, '')  | 
 | 17 | +      .replace(/[^a-z0-9]/gi, '_')  | 
 | 18 | +      .toLowerCase() + '.md'  | 
 | 19 | +      | 
 | 20 | +    const filePath = path.join(STORAGE_DIR, filename)  | 
 | 21 | +    await fs.writeFile(filePath, content, 'utf-8')  | 
44 | 22 | 
 
  | 
45 |  | -    // Save markdown file  | 
46 |  | -    const markdownPath = filepath.replace('.json', '.md')  | 
47 |  | -    fs.writeFileSync(markdownPath, content)  | 
48 |  | - | 
49 | 23 |     return NextResponse.json({ success: true })  | 
50 | 24 |   } catch (error) {  | 
51 |  | -    console.error('Error saving markdown:', error)  | 
52 | 25 |     return NextResponse.json(  | 
53 |  | -      { error: 'Failed to save markdown' },  | 
 | 26 | +      { success: false, error: error instanceof Error ? error.message : 'Failed to save markdown' },  | 
54 | 27 |       { status: 500 }  | 
55 | 28 |     )  | 
56 | 29 |   }  | 
57 | 30 | }  | 
58 | 31 | 
 
  | 
59 |  | -// GET /api/storage - List files  | 
60 |  | -// GET /api/storage?url=... - Load specific file  | 
61 |  | -export async function GET(request: NextRequest) {  | 
 | 32 | +export async function GET(request: Request) {  | 
62 | 33 |   try {  | 
63 |  | -    const url = request.nextUrl.searchParams.get('url')  | 
 | 34 | +    const { searchParams } = new URL(request.url)  | 
 | 35 | +    const url = searchParams.get('url')  | 
64 | 36 | 
 
  | 
65 |  | -    // If no URL provided, list all files  | 
 | 37 | +    // Handle list request  | 
66 | 38 |     if (!url) {  | 
67 |  | -      const files = fs.readdirSync(STORAGE_DIR)  | 
68 |  | -        .filter(file => file.endsWith('.json'))  | 
69 |  | -        .map(file => {  | 
70 |  | -          const name = file.replace('.json', '')  | 
71 |  | -          const jsonPath = `storage/markdown/${file}`  | 
72 |  | -          const markdownPath = `storage/markdown/${name}.md`  | 
 | 39 | +      // Only get .md files  | 
 | 40 | +      const files = await fs.readdir(STORAGE_DIR)  | 
 | 41 | +      const mdFiles = files.filter(f => f.endsWith('.md'))  | 
 | 42 | +        | 
 | 43 | +      const fileDetails = await Promise.all(  | 
 | 44 | +        mdFiles.map(async (filename) => {  | 
 | 45 | +          const mdPath = path.join(STORAGE_DIR, filename)  | 
 | 46 | +          const jsonPath = path.join(STORAGE_DIR, filename.replace('.md', '.json'))  | 
 | 47 | +          const stats = await fs.stat(mdPath)  | 
 | 48 | +          const content = await fs.readFile(mdPath, 'utf-8')  | 
73 | 49 | 
 
  | 
74 |  | -          // Only include if both files exist  | 
75 |  | -          if (fs.existsSync(path.join(process.cwd(), jsonPath)) &&  | 
76 |  | -              fs.existsSync(path.join(process.cwd(), markdownPath))) {  | 
77 |  | -            // Get file stats  | 
78 |  | -            const stats = fs.statSync(path.join(process.cwd(), jsonPath))  | 
79 |  | -            const content = JSON.parse(fs.readFileSync(path.join(process.cwd(), jsonPath), 'utf-8'))  | 
80 |  | -              | 
81 |  | -            // Clean up the name by removing common prefixes and file extensions  | 
82 |  | -            const cleanName = name  | 
83 |  | -              .replace(/^docs[._]/, '')  // Remove leading docs prefix  | 
84 |  | -              .replace(/\.json$/, '')    // Remove .json extension  | 
85 |  | -              .replace(/\.md$/, '')      // Remove .md extension  | 
86 |  | - | 
87 |  | -            return {  | 
88 |  | -              name: cleanName,           // Use cleaned name  | 
89 |  | -              jsonPath,  | 
90 |  | -              markdownPath,  | 
91 |  | -              timestamp: stats.mtime,  // Keep as Date for sorting  | 
92 |  | -              size: stats.size,  | 
93 |  | -              wordCount: content.stats?.wordCount || 0,  | 
94 |  | -              charCount: content.stats?.charCount || 0  | 
95 |  | -            }  | 
 | 50 | +          // Create JSON file if it doesn't exist  | 
 | 51 | +          if (!files.includes(filename.replace('.md', '.json'))) {  | 
 | 52 | +            const jsonContent = JSON.stringify({  | 
 | 53 | +              content,  | 
 | 54 | +              metadata: {  | 
 | 55 | +                wordCount: content.split(/\s+/).length,  | 
 | 56 | +                charCount: content.length,  | 
 | 57 | +                timestamp: stats.mtime  | 
 | 58 | +              }  | 
 | 59 | +            }, null, 2)  | 
 | 60 | +            await fs.writeFile(jsonPath, jsonContent, 'utf-8')  | 
 | 61 | +          }  | 
 | 62 | +            | 
 | 63 | +          return {  | 
 | 64 | +            name: filename.replace('.md', ''),  | 
 | 65 | +            jsonPath,  | 
 | 66 | +            markdownPath: mdPath,  | 
 | 67 | +            timestamp: stats.mtime,  | 
 | 68 | +            size: stats.size,  | 
 | 69 | +            wordCount: content.split(/\s+/).length,  | 
 | 70 | +            charCount: content.length  | 
96 | 71 |           }  | 
97 |  | -          return null  | 
98 | 72 |         })  | 
99 |  | -        .filter((file): file is NonNullable<typeof file> => file !== null) // Type-safe filter  | 
100 |  | -        .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) // Sort by newest first  | 
101 |  | -        | 
102 |  | -      return NextResponse.json({ files })  | 
103 |  | -    }  | 
104 |  | - | 
105 |  | -    // Load specific file  | 
106 |  | -    const filename = getFilenameFromUrl(url)  | 
107 |  | -    const filepath = path.join(STORAGE_DIR, filename)  | 
108 |  | - | 
109 |  | -    if (!fs.existsSync(filepath)) {  | 
110 |  | -      return NextResponse.json(  | 
111 |  | -        { error: 'No stored content found' },  | 
112 |  | -        { status: 404 }  | 
113 | 73 |       )  | 
 | 74 | +        | 
 | 75 | +      return NextResponse.json({  | 
 | 76 | +        success: true,  | 
 | 77 | +        files: fileDetails  | 
 | 78 | +      })  | 
114 | 79 |     }  | 
115 |  | - | 
116 |  | -    const content = fs.readFileSync(filepath, 'utf-8')  | 
117 |  | -    return NextResponse.json(JSON.parse(content))  | 
 | 80 | +      | 
 | 81 | +    // Handle single file request  | 
 | 82 | +    const filename = url  | 
 | 83 | +      .replace(/^https?:\/\//, '')  | 
 | 84 | +      .replace(/[^a-z0-9]/gi, '_')  | 
 | 85 | +      .toLowerCase() + '.md'  | 
 | 86 | +      | 
 | 87 | +    const filePath = path.join(STORAGE_DIR, filename)  | 
 | 88 | +    const content = await fs.readFile(filePath, 'utf-8')  | 
 | 89 | +      | 
 | 90 | +    return NextResponse.json({ success: true, content })  | 
118 | 91 |   } catch (error) {  | 
119 |  | -    console.error('Error loading markdown:', error)  | 
120 | 92 |     return NextResponse.json(  | 
121 |  | -      { error: 'Failed to load markdown' },  | 
 | 93 | +      { success: false, error: error instanceof Error ? error.message : 'Failed to load markdown' },  | 
122 | 94 |       { status: 500 }  | 
123 | 95 |     )  | 
124 | 96 |   }  | 
 | 
0 commit comments