diff --git a/app/(auth)/callback/route.ts b/app/(auth)/callback/route.ts deleted file mode 100644 index 937b8ab..0000000 --- a/app/(auth)/callback/route.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createClient } from "@/lib/supabase/server"; -import { NextResponse } from "next/server"; - -/** - * Handles OAuth provider callbacks and magic link confirmations. - * Supabase redirects here after the user authenticates externally. - */ -export async function GET(request: Request) { - const { searchParams, origin } = new URL(request.url); - const code = searchParams.get("code"); - - if (code) { - const supabase = await createClient(); - const { error } = await supabase.auth.exchangeCodeForSession(code); - - if (!error) { - return NextResponse.redirect(`${origin}/`); - } - } - - // Return to an error page if the code exchange fails or no code is present - return NextResponse.redirect(`${origin}/auth/error`); -} diff --git a/app/api/health/route.ts b/app/api/health/route.ts deleted file mode 100644 index 4f4d29e..0000000 --- a/app/api/health/route.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { createClient } from "@/lib/supabase/server"; -import { NextResponse } from "next/server"; - -/** - * GET /api/health - * Returns database connection status. - * Used by the home page and external health monitoring. - */ -export async function GET() { - try { - const supabase = await createClient(); - const { error } = await supabase.from("demo_items").select("id").limit(1); - - if (error) { - console.error("[health] DB query error:", error.message); - return NextResponse.json( - { status: "error", message: "Database connection failed" }, - { status: 503 }, - ); - } - - return NextResponse.json({ - status: "ok", - timestamp: new Date().toISOString(), - }); - } catch (err) { - console.error("[health] Unexpected error:", err); - return NextResponse.json( - { status: "error", message: "Internal server error" }, - { status: 500 }, - ); - } -} diff --git a/bun.lock b/bun.lock index fd82895..02b6018 100644 --- a/bun.lock +++ b/bun.lock @@ -5,8 +5,6 @@ "": { "name": "hackathon-starter", "dependencies": { - "@supabase/ssr": "^0.5.0", - "@supabase/supabase-js": "^2.49.0", "next": "^15.2.0", "openai": "^6.33.0", "react": "^19.0.0", @@ -289,20 +287,6 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="], - "@supabase/auth-js": ["@supabase/auth-js@2.97.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-2Og/1lqp+AIavr8qS2X04aSl8RBY06y4LrtIAGxat06XoXYiDxKNQMQzWDAKm1EyZFZVRNH48DO5YvIZ7la5fQ=="], - - "@supabase/functions-js": ["@supabase/functions-js@2.97.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-fSaA0ZeBUS9hMgpGZt5shIZvfs3Mvx2ZdajQT4kv/whubqDBAp3GU5W8iIXy21MRvKmO2NpAj8/Q6y+ZkZyF/w=="], - - "@supabase/postgrest-js": ["@supabase/postgrest-js@2.97.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-g4Ps0eaxZZurvfv/KGoo2XPZNpyNtjth9aW8eho9LZWM0bUuBtxPZw3ZQ6ERSpEGogshR+XNgwlSPIwcuHCNww=="], - - "@supabase/realtime-js": ["@supabase/realtime-js@2.97.0", "", { "dependencies": { "@types/phoenix": "^1.6.6", "@types/ws": "^8.18.1", "tslib": "2.8.1", "ws": "^8.18.2" } }, "sha512-37Jw0NLaFP0CZd7qCan97D1zWutPrTSpgWxAw6Yok59JZoxp4IIKMrPeftJ3LZHmf+ILQOPy3i0pRDHM9FY36Q=="], - - "@supabase/ssr": ["@supabase/ssr@0.5.2", "", { "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^0.7.0" }, "peerDependencies": { "@supabase/supabase-js": "^2.43.4" } }, "sha512-n3plRhr2Bs8Xun1o4S3k1CDv17iH5QY9YcoEvXX3bxV1/5XSasA0mNXYycFmADIdtdE6BG9MRjP5CGIs8qxC8A=="], - - "@supabase/storage-js": ["@supabase/storage-js@2.97.0", "", { "dependencies": { "iceberg-js": "^0.8.1", "tslib": "2.8.1" } }, "sha512-9f6NniSBfuMxOWKwEFb+RjJzkfMdJUwv9oHuFJKfe/5VJR8cd90qw68m6Hn0ImGtwG37TUO+QHtoOechxRJ1Yg=="], - - "@supabase/supabase-js": ["@supabase/supabase-js@2.97.0", "", { "dependencies": { "@supabase/auth-js": "2.97.0", "@supabase/functions-js": "2.97.0", "@supabase/postgrest-js": "2.97.0", "@supabase/realtime-js": "2.97.0", "@supabase/storage-js": "2.97.0" } }, "sha512-kTD91rZNO4LvRUHv4x3/4hNmsEd2ofkYhuba2VMUPRVef1RCmnHtm7rIws38Fg0yQnOSZOplQzafn0GSiy6GVg=="], - "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="], @@ -323,20 +307,14 @@ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/node": ["@types/node@20.19.33", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw=="], - "@types/phoenix": ["@types/phoenix@1.6.7", "", {}, "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q=="], - "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], - "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], "@vitest/coverage-v8": ["@vitest/coverage-v8@2.1.9", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", "debug": "^4.3.7", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", "magic-string": "^0.30.12", "magicast": "^0.3.5", "std-env": "^3.8.0", "test-exclude": "^7.0.1", "tinyrainbow": "^1.2.0" }, "peerDependencies": { "@vitest/browser": "2.1.9", "vitest": "2.1.9" }, "optionalPeers": ["@vitest/browser"] }, "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ=="], @@ -395,8 +373,6 @@ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="], @@ -483,8 +459,6 @@ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], - "iceberg-js": ["iceberg-js@0.8.1", "", {}, "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA=="], - "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], diff --git a/components/features/DatabaseStatus.tsx b/components/features/DatabaseStatus.tsx deleted file mode 100644 index a1230d1..0000000 --- a/components/features/DatabaseStatus.tsx +++ /dev/null @@ -1,71 +0,0 @@ -"use client"; - -import type { DemoItem } from "@/types"; - -interface DatabaseStatusProps { - items: DemoItem[] | null; - error: string | null; -} - -/** - * Renders the Supabase connection status and demo items on the home page. - * Shows three states: success (items list), error, or empty. - */ -export function DatabaseStatus({ items, error }: DatabaseStatusProps) { - if (error) { - return ( -
-

⚠ Connection error

-

{error}

-

- Check that your Supabase credentials in .env.local are correct and that your - Supabase project is not paused. -

-
- ); - } - - if (!items || items.length === 0) { - return ( -
-

No demo items found

-

- Run supabase/seed.sql in your Supabase dashboard to add demo rows. -

-
- ); - } - - return ( -
-

✓ Connected to Supabase

- -
- ); -} diff --git a/lib/supabase/client.ts b/lib/supabase/client.ts deleted file mode 100644 index 1bdda32..0000000 --- a/lib/supabase/client.ts +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createBrowserClient } from "@supabase/ssr"; - -/** - * Creates a Supabase client for use in Client Components. - * Uses the public anon key — Row Level Security controls data access. - */ -export function createClient() { - return createBrowserClient( - process.env.NEXT_PUBLIC_SUPABASE_URL ?? "", - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? "", - ); -} diff --git a/lib/supabase/middleware.ts b/lib/supabase/middleware.ts deleted file mode 100644 index b1f7b83..0000000 --- a/lib/supabase/middleware.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { createServerClient } from "@supabase/ssr"; -import { type NextRequest, NextResponse } from "next/server"; - -/** - * Refreshes the Supabase session on every request so the auth token stays - * current. Called from middleware.ts which runs on the Edge Runtime. - */ -export async function updateSession(request: NextRequest) { - let supabaseResponse = NextResponse.next({ request }); - - const supabase = createServerClient( - process.env.NEXT_PUBLIC_SUPABASE_URL ?? "", - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? "", - { - cookies: { - getAll() { - return request.cookies.getAll(); - }, - setAll(cookiesToSet: { name: string; value: string; options?: Record }[]) { - for (const { name, value } of cookiesToSet) { - request.cookies.set(name, value); - } - supabaseResponse = NextResponse.next({ request }); - for (const { name, value, options } of cookiesToSet) { - supabaseResponse.cookies.set(name, value, options); - } - }, - }, - }, - ); - - // Refresh the session — do NOT remove this call. - // It keeps the auth token alive and syncs server/client state. - await supabase.auth.getUser(); - - return supabaseResponse; -} diff --git a/lib/supabase/server.ts b/lib/supabase/server.ts deleted file mode 100644 index dc1d8dc..0000000 --- a/lib/supabase/server.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { createServerClient } from "@supabase/ssr"; -import { cookies } from "next/headers"; - -/** - * Creates a Supabase client for use in Server Components and Route Handlers. - * Reads auth session from cookies so the server can access user-specific data. - */ -export async function createClient() { - const cookieStore = await cookies(); - - return createServerClient( - process.env.NEXT_PUBLIC_SUPABASE_URL ?? "", - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? "", - { - cookies: { - getAll() { - return cookieStore.getAll(); - }, - setAll(cookiesToSet: { name: string; value: string; options?: Record }[]) { - try { - for (const { name, value, options } of cookiesToSet) { - cookieStore.set(name, value, options); - } - } catch { - // setAll is called from a Server Component — cookies can only be - // set from middleware or Route Handlers. This catch is expected. - } - }, - }, - }, - ); -} diff --git a/package.json b/package.json index a7725f5..896198e 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,6 @@ "test:coverage": "vitest --coverage" }, "dependencies": { - "@supabase/ssr": "^0.5.0", - "@supabase/supabase-js": "^2.49.0", "next": "^15.2.0", "openai": "^6.33.0", "react": "^19.0.0", diff --git a/supabase/seed.sql b/supabase/seed.sql deleted file mode 100644 index 72dafde..0000000 --- a/supabase/seed.sql +++ /dev/null @@ -1,45 +0,0 @@ --- ============================================================= --- Hackathon Starter — Demo Seed Data --- ============================================================= --- Run this once to set up the demo_items table used by the --- home page to demonstrate the Supabase connection. --- --- Option A: Supabase Dashboard → SQL Editor → paste & run --- Option B: supabase db seed (if using Supabase CLI) --- --- Once you have your own schema, delete this file or replace --- it with your own migration/seed files. --- ============================================================= - --- Create demo table -create table if not exists public.demo_items ( - id bigserial primary key, - title text not null, - created_at timestamptz not null default now() -); - --- Enable Row Level Security (always enable RLS on every table) -alter table public.demo_items enable row level security; - --- Allow anyone to read demo items (public read-only) -do $$ -begin - if not exists ( - select 1 from pg_policies - where tablename = 'demo_items' - and policyname = 'Anyone can read demo items' - ) then - create policy "Anyone can read demo items" - on public.demo_items - for select - using (true); - end if; -end $$; - --- Seed data — replace with your own content -insert into public.demo_items (title) -values - ('Welcome to your hackathon starter!'), - ('Connected to Supabase ✓'), - ('Replace this table with your own schema.') -on conflict do nothing; diff --git a/tests/api/health.test.ts b/tests/api/health.test.ts deleted file mode 100644 index 1a4924a..0000000 --- a/tests/api/health.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { describe, expect, it, vi } from "vitest"; - -// Mock the Supabase server client before importing the route -vi.mock("@/lib/supabase/server", () => ({ - createClient: vi.fn(), -})); - -// Mock next/headers (required by server.ts) -vi.mock("next/headers", () => ({ - cookies: vi.fn(() => ({ - getAll: vi.fn(() => []), - set: vi.fn(), - })), -})); - -import { createClient } from "@/lib/supabase/server"; - -describe("GET /api/health", () => { - it("returns 200 with status:ok and timestamp when DB is reachable", async () => { - const mockSelect = vi.fn().mockResolvedValue({ data: [{ id: 1 }], error: null }); - const mockFrom = vi.fn(() => ({ select: vi.fn(() => ({ limit: mockSelect })) })); - vi.mocked(createClient).mockResolvedValue({ from: mockFrom } as never); - - const { GET } = await import("@/app/api/health/route"); - const response = await GET(); - const body = await response.json(); - - expect(response.status).toBe(200); - expect(body.status).toBe("ok"); - expect(typeof body.timestamp).toBe("string"); - expect(() => new Date(body.timestamp)).not.toThrow(); - }); - - it("returns 503 with status:error when DB throws", async () => { - const mockSelect = vi - .fn() - .mockResolvedValue({ data: null, error: new Error("Connection refused") }); - const mockFrom = vi.fn(() => ({ select: vi.fn(() => ({ limit: mockSelect })) })); - vi.mocked(createClient).mockResolvedValue({ from: mockFrom } as never); - - vi.resetModules(); - vi.mock("@/lib/supabase/server", () => ({ createClient: vi.fn() })); - const { GET } = await import("@/app/api/health/route"); - const response = await GET(); - const body = await response.json(); - - expect(response.status).toBe(503); - expect(body.status).toBe("error"); - expect(typeof body.message).toBe("string"); - // Must not expose raw error details - expect(body.message).not.toContain("Connection refused"); - }); - - it("response body matches api-health.md contract shape on success", async () => { - const mockSelect = vi.fn().mockResolvedValue({ data: [{ id: 1 }], error: null }); - const mockFrom = vi.fn(() => ({ select: vi.fn(() => ({ limit: mockSelect })) })); - vi.mocked(createClient).mockResolvedValue({ from: mockFrom } as never); - - vi.resetModules(); - vi.mock("@/lib/supabase/server", () => ({ createClient: vi.fn() })); - const { GET } = await import("@/app/api/health/route"); - const response = await GET(); - const body = await response.json(); - - expect(body).toHaveProperty("status"); - expect(body).toHaveProperty("timestamp"); - expect(Object.keys(body)).toHaveLength(2); - }); -}); diff --git a/tests/components/DatabaseStatus.test.tsx b/tests/components/DatabaseStatus.test.tsx deleted file mode 100644 index 652a3ec..0000000 --- a/tests/components/DatabaseStatus.test.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { DatabaseStatus } from "@/components/features/DatabaseStatus"; -import type { DemoItem } from "@/types"; -import { render, screen } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; - -const mockItems: DemoItem[] = [ - { id: 1, title: "Welcome to your hackathon starter!", created_at: "2026-02-23T00:00:00Z" }, - { id: 2, title: "Connected to Supabase ✓", created_at: "2026-02-23T00:00:00Z" }, -]; - -describe("DatabaseStatus", () => { - it("renders success state with items list when items are provided", () => { - render(); - - expect(screen.getByRole("heading", { name: /connected/i })).toBeInTheDocument(); - expect(screen.getByText("Welcome to your hackathon starter!")).toBeInTheDocument(); - expect(screen.getByText("Connected to Supabase ✓")).toBeInTheDocument(); - }); - - it("renders error state with message when error prop is set", () => { - render(); - - expect(screen.getByText(/database connection failed/i)).toBeInTheDocument(); - // Should not show items list in error state - expect(screen.queryByText("Welcome to your hackathon starter!")).not.toBeInTheDocument(); - }); - - it("renders empty state message when items array is empty", () => { - render(); - - expect(screen.getByText(/no demo items found/i)).toBeInTheDocument(); - }); - - it("does not show error indicator in success state", () => { - render(); - - // No error indicators in success state - expect(screen.queryByRole("alert")).not.toBeInTheDocument(); - }); -}); diff --git a/vitest.config.ts b/vitest.config.ts index 78bb40a..67e4904 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -12,8 +12,6 @@ export default defineConfig({ provider: "v8", reporter: ["text", "json", "html"], include: ["lib/**/*.ts", "lib/**/*.tsx"], - // Supabase wrappers are thin SDK adapters tested via integration — exclude from thresholds - exclude: ["lib/supabase/**"], thresholds: { lines: 80, },