Files
app/app/api/nodes/[id]/route.ts
Albert d072b71eec refactor: Improve debug panel delete handler and add debug endpoint
- Refactored UserMenu debug panel delete handler to match ThoughtGalaxy pattern
- Added proper error handling with Mantine notifications
- Added loading state management during delete operations
- Created /api/nodes/debug endpoint for development debugging
- Cleaned up debug logging from DELETE endpoint

The debug panel now uses the same delete pattern as ThoughtGalaxy for consistency,
with proper error notifications and state updates.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 02:32:34 +00:00

133 lines
4.3 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { cookies } from 'next/headers';
import { Agent } from '@atproto/api';
import { connectToDB } from '@/lib/db';
import { verifySurrealJwt } from '@/lib/auth/jwt';
import { getOAuthClient } from '@/lib/auth/oauth-client';
/**
* DELETE /api/nodes/[id]
*
* Deletes a node from both ATproto (source of truth) and SurrealDB (cache).
*
* Process:
* 1. Verify user authentication and ownership
* 2. Fetch node from SurrealDB to get atp_uri
* 3. Delete post(s) from ATproto/Bluesky
* 4. Delete node from SurrealDB cache
*
* Note: ATproto is the source of truth. If ATproto deletion fails, we don't delete from cache.
*/
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const cookieStore = await cookies();
const surrealJwt = cookieStore.get('ponderants-auth')?.value;
console.log('[DELETE /api/nodes/[id]] Auth check:', {
hasSurrealJwt: !!surrealJwt,
});
if (!surrealJwt) {
console.error('[DELETE /api/nodes/[id]] Missing auth cookie');
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
}
// Verify the JWT and extract user info
const userSession = verifySurrealJwt(surrealJwt);
if (!userSession) {
console.error('[DELETE /api/nodes/[id]] Invalid JWT');
return NextResponse.json({ error: 'Invalid auth token' }, { status: 401 });
}
const { did: userDid } = userSession;
const { id } = await params;
console.log('[DELETE /api/nodes/[id]] Deleting node:', { nodeId: id, userDid });
try {
// 1. Fetch the node from SurrealDB to verify ownership and get atp_uri
const db = await connectToDB();
const nodeResult = await db.query<[Array<{ id: string; user_did: string; atp_uri: string }>]>(
'SELECT id, user_did, atp_uri FROM node WHERE id = $nodeId',
{ nodeId: id }
);
const node = nodeResult[0]?.[0];
if (!node) {
console.error('[DELETE /api/nodes/[id]] Node not found:', id);
return NextResponse.json({ error: 'Node not found' }, { status: 404 });
}
// 2. Verify ownership
if (node.user_did !== userDid) {
console.error('[DELETE /api/nodes/[id]] Unauthorized: user does not own node');
return NextResponse.json(
{ error: 'You do not have permission to delete this node' },
{ status: 403 }
);
}
// 3. Delete from ATproto (source of truth)
try {
const client = await getOAuthClient();
console.log('[DELETE /api/nodes/[id]] Got OAuth client, restoring session for DID:', userDid);
const session = await client.restore(userDid);
const agent = new Agent(session);
console.log('[DELETE /api/nodes/[id]] Successfully restored OAuth session');
// Parse the atp_uri to get repo and rkey
// Format: at://did:plc:xxx/app.bsky.feed.post/xxxxx
const atUriMatch = node.atp_uri.match(/at:\/\/([^/]+)\/([^/]+)\/(.+)/);
if (!atUriMatch) {
throw new Error(`Invalid atp_uri format: ${node.atp_uri}`);
}
const [, repo, collection, rkey] = atUriMatch;
console.log('[DELETE /api/nodes/[id]] Deleting ATproto record:', {
repo,
collection,
rkey,
});
// Delete the post from ATproto
await agent.api.com.atproto.repo.deleteRecord({
repo,
collection,
rkey,
});
console.log('[DELETE /api/nodes/[id]] ✓ Deleted post from ATproto');
} catch (error) {
console.error('[DELETE /api/nodes/[id]] ATproto deletion error:', error);
return NextResponse.json(
{ error: 'Failed to delete post from Bluesky. Node not deleted.' },
{ status: 500 }
);
}
// 4. Delete from SurrealDB cache (only after successful ATproto deletion)
try {
await db.delete(id);
console.log('[DELETE /api/nodes/[id]] ✓ Deleted node from SurrealDB cache');
} catch (error) {
console.warn('[DELETE /api/nodes/[id]] ⚠ SurrealDB cache deletion failed (non-critical):', error);
// This is non-critical since ATproto (source of truth) was successfully deleted
}
return NextResponse.json({ success: true, nodeId: id });
} catch (error) {
console.error('[DELETE /api/nodes/[id]] Error:', error);
return NextResponse.json(
{ error: 'Failed to delete node' },
{ status: 500 }
);
}
}