Spaces:
Sleeping
Sleeping
| import fs from 'fs/promises'; | |
| import { NextRequest, NextResponse } from 'next/server'; | |
| import path from 'path'; | |
| const DATA_ROOT = '/data'; | |
| interface FileInfo { | |
| name: string; | |
| type: 'file' | 'directory'; | |
| size: number; | |
| lastModified: string; | |
| path: string; | |
| } | |
| export async function GET(request: NextRequest) { | |
| try { | |
| const { searchParams } = new URL(request.url); | |
| const relativePath = searchParams.get('path') || ''; | |
| // パストラバーサル対策 | |
| const normalizedPath = path.normalize(relativePath).replace(/^(\.\.(\/|\\|$))+/, ''); | |
| const targetPath = path.join(DATA_ROOT, normalizedPath); | |
| // /data配下であることを確認 | |
| if (!targetPath.startsWith(DATA_ROOT)) { | |
| return NextResponse.json({ error: 'Invalid path' }, { status: 400 }); | |
| } | |
| try { | |
| // ディレクトリの存在確認 | |
| await fs.access(targetPath); | |
| const stat = await fs.stat(targetPath); | |
| if (!stat.isDirectory()) { | |
| return NextResponse.json({ error: 'Path is not a directory' }, { status: 400 }); | |
| } | |
| // ディレクトリ内容を取得 | |
| const entries = await fs.readdir(targetPath, { withFileTypes: true }); | |
| const fileInfos: FileInfo[] = await Promise.all( | |
| entries.map(async (entry) => { | |
| const fullPath = path.join(targetPath, entry.name); | |
| const fileStat = await fs.stat(fullPath); | |
| const relativePath = path.relative(DATA_ROOT, fullPath); | |
| return { | |
| name: entry.name, | |
| type: entry.isDirectory() ? 'directory' : 'file', | |
| size: fileStat.size, | |
| lastModified: fileStat.mtime.toISOString(), | |
| path: `/${relativePath}`, | |
| } as FileInfo; | |
| }), | |
| ); | |
| // ディレクトリを先に、その後ファイルを名前順にソート | |
| fileInfos.sort((a, b) => { | |
| if (a.type !== b.type) { | |
| return a.type === 'directory' ? -1 : 1; | |
| } | |
| return a.name.localeCompare(b.name); | |
| }); | |
| return NextResponse.json({ | |
| path: `/${normalizedPath}`, | |
| items: fileInfos, | |
| }); | |
| } catch (error) { | |
| // /dataディレクトリが存在しない場合 | |
| if ((error as NodeJS.ErrnoException).code === 'ENOENT') { | |
| return NextResponse.json( | |
| { | |
| path: `/${normalizedPath}`, | |
| items: [], | |
| message: '/dataディレクトリが存在しません(ローカル環境の場合は正常です)', | |
| }, | |
| { status: 200 }, | |
| ); | |
| } | |
| throw error; | |
| } | |
| } catch (error) { | |
| console.error('[API /admin/data/list] Error:', error); | |
| return NextResponse.json({ error: 'Failed to read directory' }, { status: 500 }); | |
| } | |
| } | |