diff --git a/.env.example b/.env.example index 214b9151a..fe981e4ab 100644 --- a/.env.example +++ b/.env.example @@ -12,6 +12,7 @@ SUPABASE_SERVICE_ROLE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ey AgCiAgICAicm9 POSTHOG_TELEMETRY_KEY="phc_zZraCicSTfeXX5b9wWQv2rWG8QB4Z3xlotOT7gFtoNi" TELEMETRY_ENABLED="true" MAX_RECURSION="15" +SUPABASE_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ey AgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE" # AI VARS RERANK_API_KEY="your_rerank_api_key" @@ -27,3 +28,12 @@ NEXT_PUBLIC_SUPABASE_URL="http://kong:8000" # External URL for Supabase (Kong pr NEXT_PUBLIC_WS_URL="ws://localhost:3001" NEXT_PUBLIC_SUPABASE_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ey AgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE" NEXT_PRIVATE_SUPABASE_SERVICE_ROLE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ey AgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" + +# TS SERVER +SERVER_PORT=3002 + +# ELECTRIC +ELECTRIC_PROXY_URL=http://localhost:3003 +ELECTRIC_PORT=3003 +ELECTRIC_INSECURE=false +ELECTRIC_SECRET=my-little-buttercup-has-the-sweetest-smile diff --git a/electric-server/docker-compose.yml b/electric-server/docker-compose.yml index 088c0f65a..8e62e6d25 100644 --- a/electric-server/docker-compose.yml +++ b/electric-server/docker-compose.yml @@ -2,7 +2,10 @@ services: electric: image: electricsql/electric ports: - - "3003:3000" # Expose Electric's HTTP API on port 3011 instead of 3000 + - "3003:3003" # Expose Electric's HTTP API on port 3003 instead of 3000 environment: DATABASE_URL: "postgresql://postgres:postgres@host.docker.internal:54322/postgres?sslmode=disable" - ELECTRIC_INSECURE: true + ELECTRIC_INSECURE: ${ELECTRIC_INSECURE:-false} + ELECTRIC_PROXY_URL: ${ELECTRIC_PROXY_URL:-http://localhost:3003} + ELECTRIC_PORT: ${ELECTRIC_PORT:-3003} + ELECTRIC_SECRET: ${ELECTRIC_SECRET:-my-little-buttercup-has-the-sweetest-smile} diff --git a/electric-server/makefile b/electric-server/makefile index dbb7229e7..9160f2f4b 100644 --- a/electric-server/makefile +++ b/electric-server/makefile @@ -1,3 +1,6 @@ +include ../.env +export + dev: docker compose stop && docker compose up -d diff --git a/server/.env.example b/server/.env.example index 3fcaf23a2..9506b4c67 100644 --- a/server/.env.example +++ b/server/.env.example @@ -1,4 +1,4 @@ PORT=3002 SUPABASE_URL="http://127.0.0.1:54321" SUPABASE_SERVICE_ROLE_KEY="" -ELECTRIC_URL="http://localhost:3003" \ No newline at end of file +ELECTRIC_PROXY_URL="http://localhost:3003" \ No newline at end of file diff --git a/server/src/api/v2/electric-shape/_helpers/electricHandler.test.ts b/server/src/api/v2/electric-shape/_helpers/electricHandler.test.ts new file mode 100644 index 000000000..6cfd71f44 --- /dev/null +++ b/server/src/api/v2/electric-shape/_helpers/electricHandler.test.ts @@ -0,0 +1,270 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { createProxiedResponse } from './electricHandler'; + +describe('createProxiedResponse', () => { + const mockFetch = vi.fn(); + let originalEnv: string | undefined; + + beforeEach(() => { + vi.stubGlobal('fetch', mockFetch); + vi.clearAllMocks(); + // Store original environment variable + originalEnv = process.env.ELECTRIC_SECRET; + // Set default secret for tests + process.env.ELECTRIC_SECRET = 'test-secret'; + }); + + afterEach(() => { + vi.restoreAllMocks(); + // Restore original environment variable + if (originalEnv !== undefined) { + process.env.ELECTRIC_SECRET = originalEnv; + } else { + process.env.ELECTRIC_SECRET = undefined; + } + }); + + it('should proxy a successful response and remove content-encoding and content-length headers', async () => { + const testUrl = new URL('https://example.com/test'); + const mockResponseBody = 'test response body'; + + // Create mock headers that include content-encoding and content-length + const mockHeaders = new Headers({ + 'content-type': 'application/json', + 'content-encoding': 'gzip', + 'content-length': '123', + 'cache-control': 'no-cache', + 'custom-header': 'custom-value', + }); + + const mockResponse = new Response(mockResponseBody, { + status: 200, + statusText: 'OK', + headers: mockHeaders, + }); + + mockFetch.mockResolvedValueOnce(mockResponse); + + const result = await createProxiedResponse(testUrl); + + // Verify fetch was called with correct URL + expect(mockFetch).toHaveBeenCalledOnce(); + expect(mockFetch).toHaveBeenCalledWith(testUrl); + + // Verify response properties + expect(await result.text()).toBe(mockResponseBody); + expect(result.status).toBe(200); + expect(result.statusText).toBe('OK'); + + // Verify headers were properly modified + expect(result.headers.has('content-encoding')).toBe(false); + expect(result.headers.has('content-length')).toBe(false); + + // Verify other headers are preserved + expect(result.headers.get('content-type')).toBe('application/json'); + expect(result.headers.get('cache-control')).toBe('no-cache'); + expect(result.headers.get('custom-header')).toBe('custom-value'); + }); + + it('should handle responses without content-encoding or content-length headers', async () => { + const testUrl = new URL('https://example.com/test'); + const mockResponseBody = 'test response body'; + + const mockHeaders = new Headers({ + 'content-type': 'text/plain', + 'cache-control': 'max-age=3600', + }); + + const mockResponse = new Response(mockResponseBody, { + status: 200, + statusText: 'OK', + headers: mockHeaders, + }); + + mockFetch.mockResolvedValueOnce(mockResponse); + + const result = await createProxiedResponse(testUrl); + + // Verify response properties + expect(await result.text()).toBe(mockResponseBody); + expect(result.status).toBe(200); + expect(result.statusText).toBe('OK'); + + // Verify headers are preserved (nothing to remove) + expect(result.headers.get('content-type')).toBe('text/plain'); + expect(result.headers.get('cache-control')).toBe('max-age=3600'); + expect(result.headers.has('content-encoding')).toBe(false); + expect(result.headers.has('content-length')).toBe(false); + }); + + it('should proxy error responses correctly', async () => { + const testUrl = new URL('https://example.com/error'); + + const mockHeaders = new Headers({ + 'content-type': 'application/json', + 'content-encoding': 'deflate', + 'content-length': '456', + }); + + const mockResponse = new Response('{"error": "Not found"}', { + status: 404, + statusText: 'Not Found', + headers: mockHeaders, + }); + + mockFetch.mockResolvedValueOnce(mockResponse); + + const result = await createProxiedResponse(testUrl); + + // Verify error response is properly proxied + expect(result.status).toBe(404); + expect(result.statusText).toBe('Not Found'); + expect(await result.text()).toBe('{"error": "Not found"}'); + + // Verify headers are still properly cleaned + expect(result.headers.has('content-encoding')).toBe(false); + expect(result.headers.has('content-length')).toBe(false); + expect(result.headers.get('content-type')).toBe('application/json'); + }); + + it('should handle responses with only content-encoding header', async () => { + const testUrl = new URL('https://example.com/test'); + + const mockHeaders = new Headers({ + 'content-type': 'application/json', + 'content-encoding': 'br', + }); + + const mockResponse = new Response('compressed data', { + status: 200, + statusText: 'OK', + headers: mockHeaders, + }); + + mockFetch.mockResolvedValueOnce(mockResponse); + + const result = await createProxiedResponse(testUrl); + + // Verify only content-encoding is removed + expect(result.headers.has('content-encoding')).toBe(false); + expect(result.headers.has('content-length')).toBe(false); // Should be false (wasn't present) + expect(result.headers.get('content-type')).toBe('application/json'); + }); + + it('should handle responses with only content-length header', async () => { + const testUrl = new URL('https://example.com/test'); + + const mockHeaders = new Headers({ + 'content-type': 'text/html', + 'content-length': '789', + }); + + const mockResponse = new Response('', { + status: 200, + statusText: 'OK', + headers: mockHeaders, + }); + + mockFetch.mockResolvedValueOnce(mockResponse); + + const result = await createProxiedResponse(testUrl); + + // Verify only content-length is removed + expect(result.headers.has('content-length')).toBe(false); + expect(result.headers.has('content-encoding')).toBe(false); // Should be false (wasn't present) + expect(result.headers.get('content-type')).toBe('text/html'); + }); + + it('should preserve all other headers', async () => { + const testUrl = new URL('https://example.com/test'); + + const mockHeaders = new Headers({ + 'content-type': 'application/json', + 'content-encoding': 'gzip', + 'content-length': '100', + authorization: 'Bearer token123', + 'x-custom-header': 'custom-value', + 'cache-control': 'private, max-age=0', + etag: '"abc123"', + 'last-modified': 'Wed, 21 Oct 2015 07:28:00 GMT', + }); + + const mockResponse = new Response('response data', { + status: 200, + statusText: 'OK', + headers: mockHeaders, + }); + + mockFetch.mockResolvedValueOnce(mockResponse); + + const result = await createProxiedResponse(testUrl); + + // Verify the problematic headers are removed + expect(result.headers.has('content-encoding')).toBe(false); + expect(result.headers.has('content-length')).toBe(false); + + // Verify all other headers are preserved + expect(result.headers.get('content-type')).toBe('application/json'); + expect(result.headers.get('authorization')).toBe('Bearer token123'); + expect(result.headers.get('x-custom-header')).toBe('custom-value'); + expect(result.headers.get('cache-control')).toBe('private, max-age=0'); + expect(result.headers.get('etag')).toBe('"abc123"'); + expect(result.headers.get('last-modified')).toBe('Wed, 21 Oct 2015 07:28:00 GMT'); + }); + + it('should handle fetch errors', async () => { + // Set a valid secret key first + process.env.ELECTRIC_SECRET = 'test-secret'; + + const testUrl = new URL('https://example.com/error'); + const fetchError = new Error('Network error'); + + mockFetch.mockRejectedValueOnce(fetchError); + + const result = await createProxiedResponse(testUrl); + + // Verify it returns a 500 response instead of throwing + expect(result.status).toBe(500); + expect(await result.text()).toBe('Internal Server Error'); + expect(mockFetch).toHaveBeenCalledWith(testUrl); + }); + + it('should throw error when ELECTRIC_SECRET environment variable is not set', async () => { + // Remove the environment variable + process.env.ELECTRIC_SECRET = ''; + + const testUrl = new URL('https://example.com/test'); + + await expect(createProxiedResponse(testUrl)).rejects.toThrow('ELECTRIC_SECRET is not set'); + + // Verify fetch was never called since error is thrown before + expect(mockFetch).not.toHaveBeenCalled(); + }); + + it('should add secret key to URL params when ELECTRIC_SECRET is set', async () => { + const secretKey = 'test-secret-key-123'; + process.env.ELECTRIC_SECRET = secretKey; + + const testUrl = new URL('https://example.com/test'); + const mockResponseBody = 'test response'; + + const mockResponse = new Response(mockResponseBody, { + status: 200, + statusText: 'OK', + headers: new Headers({ 'content-type': 'application/json' }), + }); + + mockFetch.mockResolvedValueOnce(mockResponse); + + const result = await createProxiedResponse(testUrl); + + // Verify fetch was called with URL that has secret parameter + expect(mockFetch).toHaveBeenCalledOnce(); + const calledUrl = mockFetch.mock.calls[0][0] as URL; + expect(calledUrl.searchParams.get('secret')).toBe(secretKey); + + // Verify the response is still valid + expect(await result.text()).toBe(mockResponseBody); + expect(result.status).toBe(200); + }); +}); diff --git a/server/src/api/v2/electric-shape/_helpers/electricHandler.ts b/server/src/api/v2/electric-shape/_helpers/electricHandler.ts new file mode 100644 index 000000000..5bea5052e --- /dev/null +++ b/server/src/api/v2/electric-shape/_helpers/electricHandler.ts @@ -0,0 +1,29 @@ +export const createProxiedResponse = async (url: URL) => { + const secretKey = process.env.ELECTRIC_SECRET; + + if (!secretKey) { + throw new Error('ELECTRIC_SECRET is not set'); + } + + url.searchParams.set('secret', secretKey); + + const response = await fetch(url).catch((error) => { + console.error('Error fetching from Electric:', error); + return new Response('Internal Server Error', { status: 500 }); + }); + + // Fetch decompresses the body but doesn't remove the + // content-encoding & content-length headers which would + // break decoding in the browser. + // See https://github.com/whatwg/fetch/issues/1729 + const headers = new Headers(response.headers); + headers.delete('content-encoding'); + headers.delete('content-length'); + + // Return the proxied response + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers, + }); +}; diff --git a/server/src/api/v2/electric-shape/_helpers.test.ts b/server/src/api/v2/electric-shape/_helpers/helpers.test.ts similarity index 57% rename from server/src/api/v2/electric-shape/_helpers.test.ts rename to server/src/api/v2/electric-shape/_helpers/helpers.test.ts index fb09a69fa..a84b2ff6c 100644 --- a/server/src/api/v2/electric-shape/_helpers.test.ts +++ b/server/src/api/v2/electric-shape/_helpers/helpers.test.ts @@ -1,33 +1,33 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { createProxiedResponse, extractParamFromWhere, getElectricShapeUrl } from './_helpers'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { extractParamFromWhere, getElectricShapeUrl } from '.'; describe('getElectricShapeUrl', () => { - process.env.ELECTRIC_URL = 'http://localhost:3000'; - const originalElectricUrl = process.env.ELECTRIC_URL; + process.env.ELECTRIC_PROXY_URL = 'http://localhost:3000'; + const originalElectricUrl = process.env.ELECTRIC_PROXY_URL; beforeEach(() => { // Clean up environment variable before each test - process.env.ELECTRIC_URL = undefined; + process.env.ELECTRIC_PROXY_URL = undefined; }); afterEach(() => { // Restore original environment variable after each test if (originalElectricUrl !== undefined) { - process.env.ELECTRIC_URL = originalElectricUrl; + process.env.ELECTRIC_PROXY_URL = originalElectricUrl; } else { - process.env.ELECTRIC_URL = undefined; + process.env.ELECTRIC_PROXY_URL = undefined; } }); - it('should return default URL with /v1/shape path when no ELECTRIC_URL is set', () => { + it('should return default URL with /v1/shape path when no ELECTRIC_PROXY_URL is set', () => { const requestUrl = 'http://example.com/test?table=users'; const result = getElectricShapeUrl(requestUrl); expect(result.toString()).toBe('http://localhost:3000/v1/shape?table=users'); }); - it('should use ELECTRIC_URL environment variable when set', () => { - process.env.ELECTRIC_URL = 'https://electric.example.com'; + it('should use ELECTRIC_PROXY_URL environment variable when set', () => { + process.env.ELECTRIC_PROXY_URL = 'https://electric.example.com'; const requestUrl = 'http://example.com/test?table=users&live=true'; const result = getElectricShapeUrl(requestUrl); @@ -138,16 +138,16 @@ describe('getElectricShapeUrl', () => { expect(result.searchParams.get('table')).toBe('posts'); }); - it('should handle ELECTRIC_URL with trailing slash', () => { - process.env.ELECTRIC_URL = 'https://electric.example.com/'; + it('should handle ELECTRIC_PROXY_URL with trailing slash', () => { + process.env.ELECTRIC_PROXY_URL = 'https://electric.example.com/'; const requestUrl = 'http://example.com/test?table=users'; const result = getElectricShapeUrl(requestUrl); expect(result.toString()).toBe('https://electric.example.com/v1/shape?table=users'); }); - it('should handle ELECTRIC_URL with path', () => { - process.env.ELECTRIC_URL = 'https://api.example.com/electric'; + it('should handle ELECTRIC_PROXY_URL with path', () => { + process.env.ELECTRIC_PROXY_URL = 'https://api.example.com/electric'; const requestUrl = 'http://example.com/test?table=users'; const result = getElectricShapeUrl(requestUrl); @@ -155,219 +155,8 @@ describe('getElectricShapeUrl', () => { }); }); -describe('createProxiedResponse', () => { - const mockFetch = vi.fn(); - - beforeEach(() => { - vi.stubGlobal('fetch', mockFetch); - vi.clearAllMocks(); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); - - it('should proxy a successful response and remove content-encoding and content-length headers', async () => { - const testUrl = new URL('https://example.com/test'); - const mockResponseBody = 'test response body'; - - // Create mock headers that include content-encoding and content-length - const mockHeaders = new Headers({ - 'content-type': 'application/json', - 'content-encoding': 'gzip', - 'content-length': '123', - 'cache-control': 'no-cache', - 'custom-header': 'custom-value', - }); - - const mockResponse = new Response(mockResponseBody, { - status: 200, - statusText: 'OK', - headers: mockHeaders, - }); - - mockFetch.mockResolvedValueOnce(mockResponse); - - const result = await createProxiedResponse(testUrl); - - // Verify fetch was called with correct URL - expect(mockFetch).toHaveBeenCalledOnce(); - expect(mockFetch).toHaveBeenCalledWith(testUrl); - - // Verify response properties - expect(await result.text()).toBe(mockResponseBody); - expect(result.status).toBe(200); - expect(result.statusText).toBe('OK'); - - // Verify headers were properly modified - expect(result.headers.has('content-encoding')).toBe(false); - expect(result.headers.has('content-length')).toBe(false); - - // Verify other headers are preserved - expect(result.headers.get('content-type')).toBe('application/json'); - expect(result.headers.get('cache-control')).toBe('no-cache'); - expect(result.headers.get('custom-header')).toBe('custom-value'); - }); - - it('should handle responses without content-encoding or content-length headers', async () => { - const testUrl = new URL('https://example.com/test'); - const mockResponseBody = 'test response body'; - - const mockHeaders = new Headers({ - 'content-type': 'text/plain', - 'cache-control': 'max-age=3600', - }); - - const mockResponse = new Response(mockResponseBody, { - status: 200, - statusText: 'OK', - headers: mockHeaders, - }); - - mockFetch.mockResolvedValueOnce(mockResponse); - - const result = await createProxiedResponse(testUrl); - - // Verify response properties - expect(await result.text()).toBe(mockResponseBody); - expect(result.status).toBe(200); - expect(result.statusText).toBe('OK'); - - // Verify headers are preserved (nothing to remove) - expect(result.headers.get('content-type')).toBe('text/plain'); - expect(result.headers.get('cache-control')).toBe('max-age=3600'); - expect(result.headers.has('content-encoding')).toBe(false); - expect(result.headers.has('content-length')).toBe(false); - }); - - it('should proxy error responses correctly', async () => { - const testUrl = new URL('https://example.com/error'); - - const mockHeaders = new Headers({ - 'content-type': 'application/json', - 'content-encoding': 'deflate', - 'content-length': '456', - }); - - const mockResponse = new Response('{"error": "Not found"}', { - status: 404, - statusText: 'Not Found', - headers: mockHeaders, - }); - - mockFetch.mockResolvedValueOnce(mockResponse); - - const result = await createProxiedResponse(testUrl); - - // Verify error response is properly proxied - expect(result.status).toBe(404); - expect(result.statusText).toBe('Not Found'); - expect(await result.text()).toBe('{"error": "Not found"}'); - - // Verify headers are still properly cleaned - expect(result.headers.has('content-encoding')).toBe(false); - expect(result.headers.has('content-length')).toBe(false); - expect(result.headers.get('content-type')).toBe('application/json'); - }); - - it('should handle responses with only content-encoding header', async () => { - const testUrl = new URL('https://example.com/test'); - - const mockHeaders = new Headers({ - 'content-type': 'application/json', - 'content-encoding': 'br', - }); - - const mockResponse = new Response('compressed data', { - status: 200, - statusText: 'OK', - headers: mockHeaders, - }); - - mockFetch.mockResolvedValueOnce(mockResponse); - - const result = await createProxiedResponse(testUrl); - - // Verify only content-encoding is removed - expect(result.headers.has('content-encoding')).toBe(false); - expect(result.headers.has('content-length')).toBe(false); // Should be false (wasn't present) - expect(result.headers.get('content-type')).toBe('application/json'); - }); - - it('should handle responses with only content-length header', async () => { - const testUrl = new URL('https://example.com/test'); - - const mockHeaders = new Headers({ - 'content-type': 'text/html', - 'content-length': '789', - }); - - const mockResponse = new Response('', { - status: 200, - statusText: 'OK', - headers: mockHeaders, - }); - - mockFetch.mockResolvedValueOnce(mockResponse); - - const result = await createProxiedResponse(testUrl); - - // Verify only content-length is removed - expect(result.headers.has('content-length')).toBe(false); - expect(result.headers.has('content-encoding')).toBe(false); // Should be false (wasn't present) - expect(result.headers.get('content-type')).toBe('text/html'); - }); - - it('should preserve all other headers', async () => { - const testUrl = new URL('https://example.com/test'); - - const mockHeaders = new Headers({ - 'content-type': 'application/json', - 'content-encoding': 'gzip', - 'content-length': '100', - authorization: 'Bearer token123', - 'x-custom-header': 'custom-value', - 'cache-control': 'private, max-age=0', - etag: '"abc123"', - 'last-modified': 'Wed, 21 Oct 2015 07:28:00 GMT', - }); - - const mockResponse = new Response('response data', { - status: 200, - statusText: 'OK', - headers: mockHeaders, - }); - - mockFetch.mockResolvedValueOnce(mockResponse); - - const result = await createProxiedResponse(testUrl); - - // Verify the problematic headers are removed - expect(result.headers.has('content-encoding')).toBe(false); - expect(result.headers.has('content-length')).toBe(false); - - // Verify all other headers are preserved - expect(result.headers.get('content-type')).toBe('application/json'); - expect(result.headers.get('authorization')).toBe('Bearer token123'); - expect(result.headers.get('x-custom-header')).toBe('custom-value'); - expect(result.headers.get('cache-control')).toBe('private, max-age=0'); - expect(result.headers.get('etag')).toBe('"abc123"'); - expect(result.headers.get('last-modified')).toBe('Wed, 21 Oct 2015 07:28:00 GMT'); - }); - - it('should handle fetch errors', async () => { - const testUrl = new URL('https://example.com/error'); - const fetchError = new Error('Network error'); - - mockFetch.mockRejectedValueOnce(fetchError); - - await expect(createProxiedResponse(testUrl)).rejects.toThrow('Network error'); - expect(mockFetch).toHaveBeenCalledWith(testUrl); - }); -}); - describe('extractParamFromWhere', () => { - it('should', () => { + it('should handle a simple where clause', () => { const testClause = "where=id='420226c8-b91d-49c5-99f8-660b04cc8c01'&offset=-1"; const url = new URL(`https://example.com/test?${testClause}`); const result = extractParamFromWhere(url, 'id'); diff --git a/server/src/api/v2/electric-shape/_helpers.ts b/server/src/api/v2/electric-shape/_helpers/helpers.ts similarity index 57% rename from server/src/api/v2/electric-shape/_helpers.ts rename to server/src/api/v2/electric-shape/_helpers/helpers.ts index 2e5990c87..6810b5529 100644 --- a/server/src/api/v2/electric-shape/_helpers.ts +++ b/server/src/api/v2/electric-shape/_helpers/helpers.ts @@ -1,10 +1,10 @@ export const getElectricShapeUrl = (requestUrl: string) => { const url = new URL(requestUrl); - const electricUrl = process.env.ELECTRIC_URL; + const electricUrl = process.env.ELECTRIC_PROXY_URL; if (!electricUrl) { - throw new Error('ELECTRIC_URL is not set'); + throw new Error('ELECTRIC_PROXY_URL is not set'); } const baseUrl = @@ -39,32 +39,6 @@ export const getElectricShapeUrl = (requestUrl: string) => { return originUrl; }; -export const createProxiedResponse = async (url: URL) => { - const response = await fetch(url); - - // Fetch decompresses the body but doesn't remove the - // content-encoding & content-length headers which would - // break decoding in the browser. - - // See https://github.com/whatwg/fetch/issues/1729 - const headers = new Headers(response.headers); - headers.delete('content-encoding'); - headers.delete('content-length'); - - // Return the proxied response - return new Response(response.body, { - status: response.status, - statusText: response.statusText, - headers, - }); -}; - -/** - * Extracts a parameter value from the where clause in the URL - * @param url - The URL object containing the where parameter - * @param paramName - The parameter name to extract (e.g., 'chatId', 'userId') - * @returns The parameter value or null if not found - */ export const extractParamFromWhere = (url: URL, paramName: string): string | null => { const whereClause = url.searchParams.get('where'); diff --git a/server/src/api/v2/electric-shape/_helpers/index.ts b/server/src/api/v2/electric-shape/_helpers/index.ts new file mode 100644 index 000000000..66e580693 --- /dev/null +++ b/server/src/api/v2/electric-shape/_helpers/index.ts @@ -0,0 +1,2 @@ +export * from './helpers'; +export * from './electricHandler'; diff --git a/server/src/middleware/auth.ts b/server/src/middleware/auth.ts index aa1c74cc3..ff42e637f 100644 --- a/server/src/middleware/auth.ts +++ b/server/src/middleware/auth.ts @@ -8,9 +8,6 @@ export const requireAuth = bearerAuth({ verifyToken: async (token, c) => { const { data, error } = await supabase.auth.getUser(token); //usually takes about 3 - 7ms - if (token.includes('Cj0g')) { - } - if (error || !data.user) { return false; } diff --git a/server/test-docker.sh b/server/test-docker.sh index 28a4b998e..a2dbe56ea 100755 --- a/server/test-docker.sh +++ b/server/test-docker.sh @@ -50,7 +50,7 @@ NODE_ENV=production SERVER_PORT=3002 SUPABASE_URL=http://localhost:54321 SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ey AgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q -ELECTRIC_URL=http://localhost:3003 +ELECTRIC_PROXY_URL=http://localhost:3003 DATABASE_URL=http://localhost:54321 EOF