- 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>
133 lines
4.3 KiB
TypeScript
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 }
|
|
);
|
|
}
|
|
}
|