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>
This commit is contained in:
@@ -49,6 +49,7 @@ export async function DELETE(
|
||||
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 }
|
||||
|
||||
66
app/api/nodes/debug/route.ts
Normal file
66
app/api/nodes/debug/route.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { connectToDB } from '@/lib/db';
|
||||
import { verifySurrealJwt } from '@/lib/auth/jwt';
|
||||
|
||||
/**
|
||||
* GET /api/nodes/debug
|
||||
*
|
||||
* Debug endpoint to list all nodes for the current user in SurrealDB.
|
||||
* Only available in development mode.
|
||||
*/
|
||||
export async function GET() {
|
||||
// Only allow in development
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
return NextResponse.json({ error: 'Not available in production' }, { status: 403 });
|
||||
}
|
||||
|
||||
const cookieStore = await cookies();
|
||||
const surrealJwt = cookieStore.get('ponderants-auth')?.value;
|
||||
|
||||
console.log('[DEBUG /api/nodes/debug] Auth check:', {
|
||||
hasSurrealJwt: !!surrealJwt,
|
||||
});
|
||||
|
||||
if (!surrealJwt) {
|
||||
console.error('[DEBUG /api/nodes/debug] 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('[DEBUG /api/nodes/debug] Invalid JWT');
|
||||
return NextResponse.json({ error: 'Invalid auth token' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { did: userDid } = userSession;
|
||||
|
||||
try {
|
||||
const db = await connectToDB();
|
||||
|
||||
// Fetch ALL nodes for this user (no filters)
|
||||
const nodesResult = await db.query<
|
||||
[Array<{ id: string; title: string; body: string; user_did: string; atp_uri: string }>]
|
||||
>('SELECT id, title, body, user_did, atp_uri FROM node WHERE user_did = $userDid', {
|
||||
userDid,
|
||||
});
|
||||
|
||||
const nodes = nodesResult[0] || [];
|
||||
|
||||
console.log('[DEBUG /api/nodes/debug] Found nodes:', {
|
||||
count: nodes.length,
|
||||
userDid,
|
||||
nodeIds: nodes.map((n) => n.id),
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
nodes,
|
||||
userDid,
|
||||
count: nodes.length,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[DEBUG /api/nodes/debug] Error:', error);
|
||||
return NextResponse.json({ error: 'Failed to fetch nodes' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Menu, Avatar, NavLink, ActionIcon, SegmentedControl, Text } from '@mantine/core';
|
||||
import { Menu, Avatar, NavLink, ActionIcon, SegmentedControl, Text, Stack, ScrollArea, Code } from '@mantine/core';
|
||||
import { useMantineColorScheme } from '@mantine/core';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { IconSun, IconMoon, IconDeviceDesktop } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
@@ -13,11 +14,20 @@ interface UserProfile {
|
||||
avatar: string | null;
|
||||
}
|
||||
|
||||
interface Node {
|
||||
id: string;
|
||||
title: string;
|
||||
user_did: string;
|
||||
}
|
||||
|
||||
export function UserMenu({ showLabel = false }: { showLabel?: boolean } = {}) {
|
||||
const router = useRouter();
|
||||
const { colorScheme, setColorScheme } = useMantineColorScheme();
|
||||
const [profile, setProfile] = useState<UserProfile | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [nodes, setNodes] = useState<Node[]>([]);
|
||||
const [nodesLoading, setNodesLoading] = useState(false);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch user profile on mount
|
||||
@@ -36,6 +46,62 @@ export function UserMenu({ showLabel = false }: { showLabel?: boolean } = {}) {
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Fetch user's nodes for debugging
|
||||
const fetchNodes = async () => {
|
||||
setNodesLoading(true);
|
||||
try {
|
||||
const response = await fetch('/api/nodes/debug', {
|
||||
credentials: 'include',
|
||||
});
|
||||
const data = await response.json();
|
||||
if (!data.error) {
|
||||
setNodes(data.nodes || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch nodes:', error);
|
||||
} finally {
|
||||
setNodesLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Delete a node (debug) - Matches ThoughtGalaxy delete pattern
|
||||
const handleDebugDelete = async (nodeId: string) => {
|
||||
setIsDeleting(true);
|
||||
|
||||
try {
|
||||
// Extract clean ID from SurrealDB RecordId format (removes angle brackets ⟨⟩)
|
||||
const cleanId = String(nodeId).replace(/[⟨⟩]/g, '');
|
||||
|
||||
const response = await fetch(`/api/nodes/${cleanId}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error || 'Failed to delete node');
|
||||
}
|
||||
|
||||
notifications.show({
|
||||
title: 'Node deleted',
|
||||
message: 'Node has been deleted from Bluesky and your galaxy',
|
||||
color: 'green',
|
||||
});
|
||||
|
||||
// Update local state to remove the deleted node
|
||||
setNodes((prevNodes) => prevNodes.filter((n) => n.id !== nodeId));
|
||||
} catch (error) {
|
||||
console.error('[UserMenu Debug] Delete error:', error);
|
||||
notifications.show({
|
||||
title: 'Delete failed',
|
||||
message: error instanceof Error ? error.message : 'Failed to delete node',
|
||||
color: 'red',
|
||||
});
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await fetch('/api/auth/logout', { method: 'POST' });
|
||||
@@ -173,6 +239,68 @@ export function UserMenu({ showLabel = false }: { showLabel?: boolean } = {}) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Debug: Show all nodes */}
|
||||
{process.env.NODE_ENV === 'development' && (
|
||||
<>
|
||||
<Menu.Divider />
|
||||
<Menu.Label>Debug: SurrealDB Nodes</Menu.Label>
|
||||
<div style={{ padding: '8px 12px' }}>
|
||||
<Text size="xs" fw={500} c="dimmed" mb={8}>
|
||||
<button
|
||||
onClick={fetchNodes}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
color: 'inherit',
|
||||
textDecoration: 'underline',
|
||||
cursor: 'pointer',
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
{nodesLoading ? 'Loading...' : `Fetch Nodes (${nodes.length})`}
|
||||
</button>
|
||||
</Text>
|
||||
{nodes.length > 0 && (
|
||||
<ScrollArea h={200}>
|
||||
<Stack gap="xs">
|
||||
{nodes.map((node) => (
|
||||
<div key={node.id} style={{ fontSize: '0.7rem', marginBottom: '8px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '4px' }}>
|
||||
<Text size="xs" fw={600}>{node.title}</Text>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (confirm(`Delete "${node.title}"?`)) {
|
||||
handleDebugDelete(node.id);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
background: '#fa5252',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
padding: '2px 6px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '0.65rem',
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
<Code block style={{ fontSize: '0.65rem' }}>{node.id}</Code>
|
||||
</div>
|
||||
))}
|
||||
</Stack>
|
||||
</ScrollArea>
|
||||
)}
|
||||
{nodes.length === 0 && !nodesLoading && (
|
||||
<Text size="xs" c="red">
|
||||
No nodes found in SurrealDB
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Menu.Divider />
|
||||
<Menu.Item onClick={handleLogout} c="red">
|
||||
Log out
|
||||
|
||||
14
todo.md
14
todo.md
@@ -6,11 +6,15 @@ Upcoming items that should be implemented (time-permitting):
|
||||
playwright mcp testing as well as that of magnitude
|
||||
- ADD MAGNITUDE TESTS FOR EVERYTHING, both existing and new additions
|
||||
- stream the AI output to deepgram for faster synthesis
|
||||
- fix the freaking galaxy node clicking -- when going directly to a node ID
|
||||
link, it redirects to /chat; when clicking on a node in /galaxy (either
|
||||
general or on a specific node ID url there), it closes the modal automatically
|
||||
- dark mode/light mode favicon and overall app theme
|
||||
- dark mode/light mode favicon
|
||||
- fix the double border on desktop between sidebar and conversation actions UI
|
||||
- delete "backup"/"old" page.tsx files
|
||||
- allow ai to transition to edit in chat
|
||||
- why wait for three nodes before umap?
|
||||
- fix creation/display of node links
|
||||
- render markdown
|
||||
- fix the "new tables being created instead of adding to the proper table"
|
||||
issues we're having with the other tables like we were having with the node
|
||||
table and we're now having with at least the oauth session, oauth state, and
|
||||
user tabless; it's probably happening with the link_to table as well but that
|
||||
one doesn't have data because it seems like link creation is broken (see task
|
||||
above to fix)
|
||||
|
||||
Reference in New Issue
Block a user