File size: 7,152 Bytes
8af739b
 
 
 
e099ac1
 
 
 
8af739b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d596ecb
 
 
 
 
 
 
 
 
 
 
 
 
8af739b
 
 
 
 
 
 
 
d596ecb
 
8af739b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ff732bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8af739b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
import { NextRequest, NextResponse } from 'next/server'
import fs from 'fs'
import path from 'path'

// Use /data for Hugging Face Spaces persistent storage
const DATA_DIR = process.env.SPACE_ID
  ? '/data'
  : path.join(process.cwd(), 'public', 'data')
const PUBLIC_DIR = path.join(DATA_DIR, 'public')

// Ensure public directory exists
if (!fs.existsSync(PUBLIC_DIR)) {
  fs.mkdirSync(PUBLIC_DIR, { recursive: true })
}

interface FileItem {
  name: string
  type: 'file' | 'folder'
  size?: number
  modified?: string
  path: string
  extension?: string
  uploadedBy?: string
  uploadedAt?: string
}

function getFileExtension(filename: string): string {
  const ext = path.extname(filename).toLowerCase()
  return ext.startsWith('.') ? ext.substring(1) : ext
}

export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams
  const folder = searchParams.get('folder') || ''

  try {
    const targetDir = path.join(PUBLIC_DIR, folder)

    // Security check - prevent directory traversal
    if (!targetDir.startsWith(PUBLIC_DIR)) {
      return NextResponse.json({ error: 'Invalid path' }, { status: 400 })
    }

    if (!fs.existsSync(targetDir)) {
      fs.mkdirSync(targetDir, { recursive: true })
    }

    const files: FileItem[] = []
    const items = fs.readdirSync(targetDir)

    for (const item of items) {
      // Skip hidden files
      if (item.startsWith('.')) continue

      const fullPath = path.join(targetDir, item)
      const relativePath = path.join(folder, item).replace(/\\/g, '/')
      const stats = fs.statSync(fullPath)

      if (stats.isDirectory()) {
        files.push({
          name: item,
          type: 'folder',
          path: relativePath,
          modified: stats.mtime.toISOString()
        })
      } else {
        // Try to read metadata if it exists
        const metadataPath = fullPath + '.meta.json'
        let metadata: any = {}
        if (fs.existsSync(metadataPath)) {
          try {
            metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'))
          } catch (e) {
            // Ignore metadata errors
          }
        }

        // Read content for text-based files
        let content = undefined
        const ext = path.extname(item).toLowerCase()
        const textExtensions = ['.json', '.tex', '.dart', '.txt', '.md', '.html', '.css', '.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.c', '.cpp', '.h']

        if (textExtensions.includes(ext)) {
          try {
            content = fs.readFileSync(fullPath, 'utf-8')
          } catch (err) {
            console.error(`Error reading ${item}:`, err)
          }
        }

        files.push({
          name: item,
          type: 'file',
          size: stats.size,
          modified: stats.mtime.toISOString(),
          path: relativePath,
          extension: getFileExtension(item),
          uploadedBy: metadata.uploadedBy || 'Anonymous',
          uploadedAt: metadata.uploadedAt || stats.birthtime.toISOString(),
          ...(content !== undefined && { content })
        })
      }
    }

    return NextResponse.json({
      files,
      currentPath: folder,
      isPublic: true
    })
  } catch (error) {
    console.error('Error listing public files:', error)
    return NextResponse.json(
      { error: 'Failed to list public files' },
      { status: 500 }
    )
  }
}

// Upload to public folder
export async function POST(request: NextRequest) {
  try {
    const contentType = request.headers.get('content-type')

    // Handle JSON body (for save_file action)
    if (contentType?.includes('application/json')) {
      const body = await request.json()
      const { action, fileName, content, folder = '' } = body

      if (action === 'save_file') {
        if (!fileName || content === undefined) {
          return NextResponse.json({ error: 'fileName and content are required' }, { status: 400 })
        }

        const targetDir = path.join(PUBLIC_DIR, folder)

        // Security check
        if (!targetDir.startsWith(PUBLIC_DIR)) {
          return NextResponse.json({ error: 'Invalid path' }, { status: 400 })
        }

        if (!fs.existsSync(targetDir)) {
          fs.mkdirSync(targetDir, { recursive: true })
        }

        const filePath = path.join(targetDir, fileName)

        // Write file (overwrite if exists)
        fs.writeFileSync(filePath, content, 'utf-8')

        // Update or create metadata
        const metadataPath = filePath + '.meta.json'
        const existingMetadata = fs.existsSync(metadataPath)
          ? JSON.parse(fs.readFileSync(metadataPath, 'utf-8'))
          : {}

        fs.writeFileSync(metadataPath, JSON.stringify({
          ...existingMetadata,
          lastModified: new Date().toISOString()
        }))

        return NextResponse.json({ success: true })
      }
    }

    const formData = await request.formData()
    const file = formData.get('file') as File
    const folder = formData.get('folder') as string || ''
    const uploadedBy = formData.get('uploadedBy') as string || 'Anonymous'

    if (!file) {
      return NextResponse.json({ error: 'No file provided' }, { status: 400 })
    }

    const targetDir = path.join(PUBLIC_DIR, folder)

    // Security check
    if (!targetDir.startsWith(PUBLIC_DIR)) {
      return NextResponse.json({ error: 'Invalid path' }, { status: 400 })
    }

    if (!fs.existsSync(targetDir)) {
      fs.mkdirSync(targetDir, { recursive: true })
    }

    const fileName = file.name
    const filePath = path.join(targetDir, fileName)

    // Check if file already exists
    if (fs.existsSync(filePath)) {
      // Add timestamp to filename
      const timestamp = Date.now()
      const ext = path.extname(fileName)
      const baseName = path.basename(fileName, ext)
      const newFileName = `${baseName}_${timestamp}${ext}`
      const newFilePath = path.join(targetDir, newFileName)

      const buffer = Buffer.from(await file.arrayBuffer())
      fs.writeFileSync(newFilePath, buffer)

      // Save metadata
      const metadataPath = newFilePath + '.meta.json'
      fs.writeFileSync(metadataPath, JSON.stringify({
        uploadedBy,
        uploadedAt: new Date().toISOString(),
        originalName: fileName
      }))

      return NextResponse.json({
        success: true,
        message: 'File uploaded to public folder',
        path: path.join(folder, newFileName).replace(/\\/g, '/'),
        renamed: true
      })
    } else {
      const buffer = Buffer.from(await file.arrayBuffer())
      fs.writeFileSync(filePath, buffer)

      // Save metadata
      const metadataPath = filePath + '.meta.json'
      fs.writeFileSync(metadataPath, JSON.stringify({
        uploadedBy,
        uploadedAt: new Date().toISOString()
      }))

      return NextResponse.json({
        success: true,
        message: 'File uploaded to public folder',
        path: path.join(folder, fileName).replace(/\\/g, '/')
      })
    }
  } catch (error) {
    console.error('Error uploading to public folder:', error)
    return NextResponse.json(
      { error: 'Failed to upload file' },
      { status: 500 }
    )
  }
}