diff options
| author | diogo464 <[email protected]> | 2025-08-11 16:16:01 +0100 |
|---|---|---|
| committer | diogo464 <[email protected]> | 2025-08-11 16:16:01 +0100 |
| commit | 68afafc281103c32b193d5f116d87f74187bdc63 (patch) | |
| tree | eb69225e0e9d0f9edc0475933669a1dee2a3eb31 | |
| parent | 8bba15d57502e97b1b96590bd21e0e8f541eaac5 (diff) | |
add delete api endpoint with authentication
- Added Drive_remove() function to drive_server.ts using fctdrive remove command
- Created /api/delete route with POST method, JSON body input, and authentication
- Added development mode AUTH header bypass for testing authenticated endpoints
- Updated CLAUDE.md with API testing instructions using AUTH: 1 header
- Endpoint validates user authentication and upload permissions before deletion
- Returns success response even for non-existent files (CLI ignores them)
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <[email protected]>
| -rw-r--r-- | CLAUDE.md | 6 | ||||
| -rw-r--r-- | frontend/app/api/delete/route.ts | 48 | ||||
| -rw-r--r-- | frontend/lib/auth.ts | 16 | ||||
| -rw-r--r-- | frontend/lib/drive_server.ts | 11 |
4 files changed, 80 insertions, 1 deletions
| @@ -44,4 +44,8 @@ | |||
| 44 | - **Mobile**: Must be mobile-friendly | 44 | - **Mobile**: Must be mobile-friendly |
| 45 | - **Architecture**: Server-side rendering, modular reusable components | 45 | - **Architecture**: Server-side rendering, modular reusable components |
| 46 | - **Updates**: Manual refresh only (no real-time) | 46 | - **Updates**: Manual refresh only (no real-time) |
| 47 | - the server is running at `127.0.0.1:3000` and you can use curl to test the page \ No newline at end of file | 47 | - the server is running at `127.0.0.1:3000` and you can use curl to test the page |
| 48 | |||
| 49 | ## API Testing | ||
| 50 | - For testing authenticated endpoints in development, use header `AUTH: 1` | ||
| 51 | - Example: `curl -H "AUTH: 1" -H "Content-Type: application/json" -X POST localhost:3000/api/delete -d '{"path":"/file.txt"}'` \ No newline at end of file | ||
diff --git a/frontend/app/api/delete/route.ts b/frontend/app/api/delete/route.ts new file mode 100644 index 0000000..b4a27d4 --- /dev/null +++ b/frontend/app/api/delete/route.ts | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | import { NextRequest, NextResponse } from 'next/server' | ||
| 2 | import { Auth_get_user, Auth_user_can_upload } from '@/lib/auth' | ||
| 3 | import { Drive_remove } from '@/lib/drive_server' | ||
| 4 | import { revalidatePath } from 'next/cache' | ||
| 5 | |||
| 6 | export async function POST(request: NextRequest) { | ||
| 7 | try { | ||
| 8 | // Check user authentication and permissions | ||
| 9 | const user = await Auth_get_user() | ||
| 10 | if (!user.isLoggedIn) { | ||
| 11 | return NextResponse.json({ error: 'User not authenticated' }, { status: 401 }) | ||
| 12 | } | ||
| 13 | |||
| 14 | if (!Auth_user_can_upload(user)) { | ||
| 15 | return NextResponse.json({ error: 'User does not have upload permissions' }, { status: 403 }) | ||
| 16 | } | ||
| 17 | |||
| 18 | // Parse JSON body | ||
| 19 | const body = await request.json() | ||
| 20 | const path = body.path | ||
| 21 | |||
| 22 | // Validate path | ||
| 23 | if (!path || typeof path !== 'string') { | ||
| 24 | return NextResponse.json({ error: 'Path is required and must be a string' }, { status: 400 }) | ||
| 25 | } | ||
| 26 | |||
| 27 | // Remove file/directory using Drive_remove | ||
| 28 | await Drive_remove(path, user.email) | ||
| 29 | |||
| 30 | // Revalidate the parent directory to refresh listings | ||
| 31 | const parentPath = path.split('/').slice(0, -1).join('/') || '/' | ||
| 32 | revalidatePath(`/drive${parentPath}`) | ||
| 33 | revalidatePath('/drive') | ||
| 34 | |||
| 35 | return NextResponse.json({ | ||
| 36 | success: true, | ||
| 37 | message: 'Path deleted successfully', | ||
| 38 | deletedPath: path | ||
| 39 | }) | ||
| 40 | |||
| 41 | } catch (error) { | ||
| 42 | console.error('Delete error:', error) | ||
| 43 | return NextResponse.json( | ||
| 44 | { error: error instanceof Error ? error.message : 'Internal server error' }, | ||
| 45 | { status: 500 } | ||
| 46 | ) | ||
| 47 | } | ||
| 48 | } \ No newline at end of file | ||
diff --git a/frontend/lib/auth.ts b/frontend/lib/auth.ts index fe00b11..015fddf 100644 --- a/frontend/lib/auth.ts +++ b/frontend/lib/auth.ts | |||
| @@ -30,6 +30,22 @@ export async function Auth_extract_session_cookie(): Promise<UserSessionCookie | | |||
| 30 | } | 30 | } |
| 31 | 31 | ||
| 32 | export async function Auth_get_user(): Promise<UserAuth> { | 32 | export async function Auth_get_user(): Promise<UserAuth> { |
| 33 | // Development mode bypass for testing with AUTH header | ||
| 34 | if (Env_is_development()) { | ||
| 35 | const { headers } = await import('next/headers'); | ||
| 36 | const headersList = await headers(); | ||
| 37 | if (headersList.get('AUTH') === '1') { | ||
| 38 | return { | ||
| 39 | isLoggedIn: true, | ||
| 40 | username: 'testuser', | ||
| 41 | name: 'Test User', | ||
| 42 | email: '[email protected]', | ||
| 43 | provider: 'dev', | ||
| 44 | oauth: false | ||
| 45 | }; | ||
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 33 | const cookie = await Auth_extract_session_cookie(); | 49 | const cookie = await Auth_extract_session_cookie(); |
| 34 | const endpoint = Auth_tinyauth_endpoint(); | 50 | const endpoint = Auth_tinyauth_endpoint(); |
| 35 | 51 | ||
diff --git a/frontend/lib/drive_server.ts b/frontend/lib/drive_server.ts index 91c3cb4..eba2d5a 100644 --- a/frontend/lib/drive_server.ts +++ b/frontend/lib/drive_server.ts | |||
| @@ -72,4 +72,15 @@ export async function Drive_blob_path(blob: string): Promise<string> { | |||
| 72 | throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`) | 72 | throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`) |
| 73 | } | 73 | } |
| 74 | return result.stdout.trim(); | 74 | return result.stdout.trim(); |
| 75 | } | ||
| 76 | |||
| 77 | /// removes the file or directory at the given path | ||
| 78 | export async function Drive_remove(path: string, email: string) { | ||
| 79 | const result = spawnSync('fctdrive', ['remove', '--email', email, path], { encoding: 'utf-8' }); | ||
| 80 | if (result.error) { | ||
| 81 | throw new Error(`Failed to execute fctdrive: ${result.error.message}`); | ||
| 82 | } | ||
| 83 | if (result.status !== 0) { | ||
| 84 | throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`); | ||
| 85 | } | ||
| 75 | } \ No newline at end of file | 86 | } \ No newline at end of file |
