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 }); } }