diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml new file mode 100644 index 0000000..9274b2c --- /dev/null +++ b/.github/workflows/opencode-review.yml @@ -0,0 +1,30 @@ +name: opencode-review + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +jobs: + review: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + pull-requests: read + issues: read + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + - uses: anomalyco/opencode/github@latest + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + model: anthropic/claude-sonnet-4-20250514 + use_github_token: true + prompt: | + Review this pull request: + - Check for code quality issues + - Look for potential bugs + - Suggest improvements \ No newline at end of file diff --git a/.github/workflows/opencode.yml b/.github/workflows/opencode.yml index ecd27cf..bb81700 100644 --- a/.github/workflows/opencode.yml +++ b/.github/workflows/opencode.yml @@ -29,5 +29,3 @@ jobs: uses: anomalyco/opencode/github@latest env: OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - with: - model: "opencode/MiniMax-M2.5-Free" diff --git a/.planning/debug/dashboard-internal-server-error-user-id-column.md b/.planning/debug/dashboard-internal-server-error-user-id-column.md new file mode 100644 index 0000000..bfb0875 --- /dev/null +++ b/.planning/debug/dashboard-internal-server-error-user-id-column.md @@ -0,0 +1,87 @@ +--- +status: awaiting_human_verify +trigger: "Investigate issue: dashboard-internal-server-error-user-id-column" +created: 2026-04-01T21:15:34.683690+05:30 +updated: 2026-04-01T22:11:00.000000+05:30 +--- + +## Current Focus + + +hypothesis: DatabasesPage auth guard should prevent "Authentication is still loading" during initial database fetch +test: manual verification in real workflow after local lint/build confirmation +expecting: opening dashboard/databases no longer shows auth-loading error; upload/databases flow proceeds normally +next_action: ask user to verify databases page and upload flow end-to-end + +## Symptoms + + +expected: After login, dashboard loads databases list successfully. +actual: Frontend shows "Error loading databases: Internal Server Error". +errors: sqlalchemy.exc.ProgrammingError: (psycopg2.errors.UndefinedColumn) column databases.user_id does not exist while querying /api/v1/databases with WHERE databases.user_id = :user_id. +reproduction: Login with Firebase -> open dashboard -> frontend requests GET /api/v1/databases -> backend 500. +started: Started after auth/user-isolation changes that introduced user_id filtering in backend queries. + +## Eliminated + + +## Evidence + + +- timestamp: 2026-04-01T21:17:00+05:30 + checked: backend API and model references for user_id + found: /api/v1/databases router filters with DatabaseModel.user_id == user_id in multiple endpoints; Database model defines user_id field + implication: runtime SQL will reference databases.user_id whenever user-scoped queries execute + +- timestamp: 2026-04-01T21:17:30+05:30 + checked: SQL initialization and migration files + found: backend/migrations/001_add_user_id.sql adds user_id to databases and query_history; backend/init.sql currently includes user_id columns and indexes + implication: codebase expects user_id column, but environments created before migration may still miss the column if migration wasn’t executed + +- timestamp: 2026-04-01T21:21:30+05:30 + checked: backend startup/initialization flow + found: main.py calls init_db(); init_db() only does Base.metadata.create_all(bind=engine), which does not alter existing tables to add new columns + implication: pre-existing databases table can remain without user_id despite model expecting it + +- timestamp: 2026-04-01T21:22:00+05:30 + checked: migration execution wiring + found: backend/migrations SQL files are not referenced by runtime code; docker-compose mounts init.sql into docker-entrypoint-initdb.d, which runs only when postgres volume is first initialized + implication: environments with persistent postgres_data created before user_id addition will hit UndefinedColumn at query time + +- timestamp: 2026-04-01T21:26:00+05:30 + checked: backend verification commands + found: python -m compileall database/session.py succeeds; python -m pytest unavailable in local environment (pytest module missing) + implication: code is syntactically valid, but end-to-end verification requires runtime check in user environment + +- timestamp: 2026-04-01T22:02:00+05:30 + checked: human verification checkpoint response + found: original UndefinedColumn issue is fixed after restart, but DatabasesPage now fails initial fetch with frontend error "Authentication is still loading" from useApi.getAuthToken + implication: backend schema fix worked; new frontend timing/auth readiness issue blocks dashboard databases flow + +- timestamp: 2026-04-01T22:04:00+05:30 + checked: frontend/src/hooks/use-api.ts getAuthToken implementation + found: getAuthToken throws explicit error when auth context loading=true before requesting token + implication: any page fetching via useApi during auth bootstrap will deterministically fail with "Authentication is still loading" + +- timestamp: 2026-04-01T22:05:00+05:30 + checked: dashboard page comparison (frontend/src/app/dashboard/page.tsx vs frontend/src/app/dashboard/databases/page.tsx) + found: dashboard home waits for useAuth().isLoading to clear and checks isAuthenticated before api.getDatabases(); databases page calls api.getDatabases() immediately on mount without auth guard + implication: missing auth guard in databases page is the direct mechanism causing premature token request and frontend error + +- timestamp: 2026-04-01T22:08:00+05:30 + checked: frontend/src/app/dashboard/databases/page.tsx + found: added useAuth-based guard (authLoading/isAuthenticated) before fetch, with dependencies updated accordingly + implication: DatabasesPage no longer issues token-bound API request during auth bootstrap window + +- timestamp: 2026-04-01T22:10:00+05:30 + checked: frontend verification commands + found: pnpm lint passed; pnpm build passed with successful Next.js production build + implication: fix compiles cleanly and passes frontend static checks + +## Resolution + + +root_cause: DatabasesPage triggers api.getDatabases() on mount before auth-provider finishes loading, and useApi.getAuthToken intentionally throws while loading=true. +fix: Added useAuth guard in frontend/src/app/dashboard/databases/page.tsx to defer api.getDatabases() until auth loading completes and user is authenticated. +verification: Self-verified with frontend lint/build pass; pending user confirmation in live login + databases/upload workflow. +files_changed: [backend/database/session.py, frontend/src/app/dashboard/databases/page.tsx] diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts deleted file mode 100644 index af1c403..0000000 --- a/frontend/src/lib/api.ts +++ /dev/null @@ -1,177 +0,0 @@ -/** - * Server-Side API Client - * For use in Server Components and Server Actions - * Uses fetch with Node.js runtime - */ - -import { auth } from "@clerk/nextjs/server"; -import type { - DatabaseResponse, - DatabaseUploadResponse, - DeleteDatabaseResponse, - SchemaDataResponse, - DatabaseTablesResponse, - QueryRequest, - QueryResponse, - CacheStatsResponse, - CacheClearResponse, -} from "@/types/api"; - -const BACKEND_URL = process.env.BACKEND_URL || "http://localhost:8000"; - -function isExpectedDynamicServerError(error: unknown): boolean { - if (typeof error !== "object" || error === null) { - return false; - } - - const dynamicError = error as { digest?: unknown }; - return dynamicError.digest === "DYNAMIC_SERVER_USAGE"; -} - -/** - * Get authorization headers with Clerk JWT token - */ -async function getAuthHeaders(): Promise { - try { - const { getToken } = await auth(); - const token = await getToken(); - - if (token) { - return { - Authorization: `Bearer ${token}`, - }; - } - } catch (error) { - if (!isExpectedDynamicServerError(error)) { - console.warn("Failed to get auth token:", error); - } - } - - return {}; -} - -/** - * Base fetch wrapper with auth and error handling - */ -async function apiFetch( - endpoint: string, - options?: RequestInit -): Promise { - const authHeaders = await getAuthHeaders(); - - const response = await fetch(`${BACKEND_URL}${endpoint}`, { - ...options, - headers: { - ...authHeaders, - ...options?.headers, - }, - }); - - if (!response.ok) { - const error = await response.json().catch(() => ({ - detail: response.statusText, - })); - throw new Error(error.detail || `API Error: ${response.status}`); - } - - return response.json(); -} - -// ============================================================================ -// Database API -// ============================================================================ - -export async function getDatabases(): Promise { - return apiFetch("/api/v1/databases"); -} - -export async function getDatabase(databaseId: number): Promise { - return apiFetch(`/api/v1/databases/${databaseId}`); -} - -export async function uploadDatabase( - file: File, - displayName: string, - description?: string -): Promise { - const authHeaders = await getAuthHeaders(); - const formData = new FormData(); - formData.append("file", file); - formData.append("display_name", displayName); - if (description) { - formData.append("description", description); - } - - const response = await fetch(`${BACKEND_URL}/api/v1/databases/upload`, { - method: "POST", - headers: authHeaders, - body: formData, - }); - - if (!response.ok) { - const error = await response.json().catch(() => ({ - detail: response.statusText, - })); - throw new Error(error.detail || `Upload failed: ${response.status}`); - } - - return response.json(); -} - -export async function deleteDatabase( - databaseId: number -): Promise { - return apiFetch(`/api/v1/databases/${databaseId}`, { - method: "DELETE", - }); -} - -export async function getDatabaseSchema( - databaseId: number -): Promise { - return apiFetch(`/api/v1/databases/${databaseId}/schema`); -} - -export async function getDatabaseTables( - databaseId: number -): Promise { - return apiFetch( - `/api/v1/databases/${databaseId}/tables` - ); -} - -// ============================================================================ -// Query API -// ============================================================================ - -export async function queryDatabase( - databaseId: number, - question: string -): Promise { - const requestBody: QueryRequest = { question }; - - return apiFetch( - `/api/v1/databases/${databaseId}/query`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(requestBody), - } - ); -} - -// ============================================================================ -// Cache API -// ============================================================================ - -export async function getCacheStats(): Promise { - return apiFetch("/api/v1/cache/stats"); -} - -export async function clearCache(): Promise { - return apiFetch("/api/v1/cache/clear", { - method: "POST", - }); -} diff --git a/frontend/src/proxy.ts b/frontend/src/proxy.ts deleted file mode 100644 index 321a24b..0000000 --- a/frontend/src/proxy.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server"; -import { NextResponse } from "next/server"; - -const isProtectedRoute = createRouteMatcher(["/dashboard(.*)"]); - -export default clerkMiddleware(async (auth, req) => { - // Skip Clerk middleware for backend API proxy routes - // These routes are handled by Next.js rewrites and backend auth - if (req.nextUrl.pathname.startsWith("/api/v1/")) { - return NextResponse.next(); - } - - if (isProtectedRoute(req)) { - await auth.protect(); - } -}); - -export const config = { - matcher: [ - "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)", - "/(api|trpc)(.*)", - ], -};