← back to ClawCoder

src/app/api/files/[id]/route.ts

82 lines

import { NextRequest, NextResponse } from 'next/server'
import fs from 'fs'
import { authenticateRequest } from '@/lib/auth'
import { CLAUDE_MD_PATHS, ALLOWED_PATHS } from '../route'

function decodePath(id: string): string | null {
  try {
    const decoded = Buffer.from(decodeURIComponent(id), 'base64').toString('utf-8')
    // Security: only allow paths in the allowlist
    if (!ALLOWED_PATHS.has(decoded)) return null
    return decoded
  } catch {
    return null
  }
}

function getProject(filePath: string): string {
  const entry = CLAUDE_MD_PATHS.find(e => e.path === filePath)
  return entry?.project ?? 'Unknown'
}

export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const auth = authenticateRequest(request)
  if (auth instanceof NextResponse) return auth

  const { id } = await params
  const filePath = decodePath(id)
  if (!filePath) {
    return NextResponse.json({ error: 'Invalid or disallowed file path' }, { status: 403 })
  }

  try {
    const content = fs.readFileSync(filePath, 'utf-8')
    const stat = fs.statSync(filePath)
    return NextResponse.json({
      path: filePath,
      project: getProject(filePath),
      content,
      lines: content.split('\n').length,
      lastModified: stat.mtime.toISOString(),
    })
  } catch {
    return NextResponse.json({ error: 'File not found or unreadable' }, { status: 404 })
  }
}

export async function PUT(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const auth = authenticateRequest(request)
  if (auth instanceof NextResponse) return auth

  const { id } = await params
  const filePath = decodePath(id)
  if (!filePath) {
    return NextResponse.json({ error: 'Invalid or disallowed file path' }, { status: 403 })
  }

  try {
    const { content } = await request.json()
    if (typeof content !== 'string') {
      return NextResponse.json({ error: 'Content must be a string' }, { status: 400 })
    }

    fs.writeFileSync(filePath, content, 'utf-8')
    const lines = content.split('\n').length

    return NextResponse.json({
      ok: true,
      path: filePath,
      lines,
    })
  } catch (err) {
    const message = err instanceof Error ? err.message : 'Write failed'
    return NextResponse.json({ error: message }, { status: 500 })
  }
}