Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/chat/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* shared by app/api/chat and app/api/professional-chat routes.
*/

import type { SupabaseClient } from '@supabase/supabase-js';
import type { AppSupabaseClient } from '@/lib/supabase';
import { logger } from '@/lib/logger';
import { truncateChunks, joinContext } from './context';

Expand Down Expand Up @@ -38,7 +38,7 @@ export interface DocumentSearchResult {
* @param options - Optional filters (documentId, allowedDocumentIds, matchCount, maxContextChars)
*/
export async function searchUserDocuments(
supabase: SupabaseClient,
supabase: AppSupabaseClient,
embedding: number[],
userId: string,
options: {
Expand Down
15 changes: 15 additions & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,21 @@
* This file contains domain-specific constants.
*/

/**
* The Postgres schema this app's tables live in.
*
* Botsmann shares one self-hosted Supabase database with orangecat, which owns
* `public` and its 128 tables. Our names collide with theirs — `conversations`,
* `documents`, `waitlist`, and a function `update_updated_at()` that public
* already defines — so our migrations apply into a schema of our own, exactly
* as printcraft's do. Every client must be told, or it queries orangecat's
* `public` and gets PGRST205 "Could not find the table" (which is the 503
* /api/health served for months).
*
* SSOT: this constant and `supabase:botsmann` in fleetcrown's apps.conf.
*/
export const DB_SCHEMA = 'botsmann';

// Domain-specific error messages (extends api-utils ERROR_MESSAGES)
export const DOMAIN_ERRORS = {
// Service errors
Expand Down
2 changes: 2 additions & 0 deletions lib/supabase-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';
import { getClientEnv } from '@/lib/config/env';
import { DB_SCHEMA } from '@/lib/constants';

/**
* Create Supabase client for route handlers (API routes)
Expand All @@ -24,6 +25,7 @@ export async function createRouteHandlerClient(_options?: { cookies?: unknown })
const cookieStore = await cookies();

return createServerClient(supabaseUrl, supabaseAnonKey, {
db: { schema: DB_SCHEMA },
cookies: {
getAll() {
return cookieStore.getAll();
Expand Down
35 changes: 26 additions & 9 deletions lib/supabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
* Setup: https://supabase.com/dashboard
*/

import { createClient, SupabaseClient } from '@supabase/supabase-js';
import { createClient } from '@supabase/supabase-js';
import { createBrowserClient } from '@supabase/ssr';
import { getClientEnv, getServerEnv } from '@/lib/config/env';
import { DB_SCHEMA } from '@/lib/constants';

// Database row types
export interface ConsultationRow {
Expand Down Expand Up @@ -65,18 +66,33 @@ export function isSupabaseConfigured(): boolean {
return Boolean(supabaseUrl && supabaseAnonKey);
}

function createAnonClient() {
const { NEXT_PUBLIC_SUPABASE_URL: supabaseUrl, NEXT_PUBLIC_SUPABASE_ANON_KEY: supabaseAnonKey } =
getClientEnv();

return createClient(supabaseUrl, supabaseAnonKey, { db: { schema: DB_SCHEMA } });
}

/**
* A client scoped to OUR schema.
*
* The bare `SupabaseClient` type hardcodes `'public'` as its schema, so
* annotating with it silently asserts we query orangecat's tables — and the
* compiler rejects the client we actually build. Deriving the type from the
* factory keeps the two in step, and survives supabase-js changing the order
* of its generic parameters.
*/
export type AppSupabaseClient = ReturnType<typeof createAnonClient>;

// Lazy-loaded Supabase client singleton
let _supabaseClient: SupabaseClient | null = null;
let _supabaseClient: AppSupabaseClient | null = null;

export function getSupabaseClient(): SupabaseClient {
export function getSupabaseClient(): AppSupabaseClient {
if (_supabaseClient) {
return _supabaseClient;
}

const { NEXT_PUBLIC_SUPABASE_URL: supabaseUrl, NEXT_PUBLIC_SUPABASE_ANON_KEY: supabaseAnonKey } =
getClientEnv();

_supabaseClient = createClient(supabaseUrl, supabaseAnonKey);
_supabaseClient = createAnonClient();
return _supabaseClient;
}

Expand Down Expand Up @@ -105,11 +121,12 @@ export const supabase = {
};

// Server-side client with service role (for admin operations)
export function getServiceClient(): SupabaseClient {
export function getServiceClient() {
const { NEXT_PUBLIC_SUPABASE_URL: supabaseUrl } = getClientEnv();
const { SUPABASE_SERVICE_ROLE_KEY: serviceRoleKey } = getServerEnv();

return createClient(supabaseUrl, serviceRoleKey, {
db: { schema: DB_SCHEMA },
auth: {
autoRefreshToken: false,
persistSession: false,
Expand Down Expand Up @@ -187,5 +204,5 @@ export function createClientComponentClient() {
return mockClient;
}

return createBrowserClient(supabaseUrl, supabaseAnonKey);
return createBrowserClient(supabaseUrl, supabaseAnonKey, { db: { schema: DB_SCHEMA } });
}
146 changes: 0 additions & 146 deletions scripts/migrate-via-api.ts

This file was deleted.

91 changes: 0 additions & 91 deletions scripts/run-migration.ts

This file was deleted.

5 changes: 4 additions & 1 deletion scripts/test-db-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/

import { createClient } from '@supabase/supabase-js';
import { DB_SCHEMA } from '../lib/constants';
import * as dotenv from 'dotenv';
import * as path from 'path';

Expand All @@ -23,7 +24,9 @@ if (!supabaseUrl || !supabaseKey) {
process.exit(1);
}

const supabase = createClient(supabaseUrl, supabaseKey);
// Our tables live in our own schema, not orangecat's `public` — without this
// the diagnostic reports every table missing and looks like a broken database.
const supabase = createClient(supabaseUrl, supabaseKey, { db: { schema: DB_SCHEMA } });

async function testConnection() {
console.log('🔍 Testing Supabase Connection...\n');
Expand Down
Loading
Loading