Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-06-30 20:38:33 +00:00
co-authored by renee-png
parent 2a6a6e4d94
commit b6233ade1b
8 changed files with 425 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
SUPABASE_PROJECT_ID="fvjpfhfgkhgtwtzrgcgx"
SUPABASE_PUBLISHABLE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImZ2anBmaGZna2hndHd0enJnY2d4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODI4NDE0NDIsImV4cCI6MjA5ODQxNzQ0Mn0.B7dh8P2ZfUbJMtAGVbbRyyNFk71oqhJuqd0BNki-Dqk"
SUPABASE_URL="https://fvjpfhfgkhgtwtzrgcgx.supabase.co"
VITE_SUPABASE_PROJECT_ID="fvjpfhfgkhgtwtzrgcgx"
VITE_SUPABASE_PUBLISHABLE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImZ2anBmaGZna2hndHd0enJnY2d4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODI4NDE0NDIsImV4cCI6MjA5ODQxNzQ0Mn0.B7dh8P2ZfUbJMtAGVbbRyyNFk71oqhJuqd0BNki-Dqk"
VITE_SUPABASE_URL="https://fvjpfhfgkhgtwtzrgcgx.supabase.co"
@@ -0,0 +1,15 @@
// This file is automatically generated. Do not edit it directly.
import { createMiddleware } from '@tanstack/react-start'
import { supabase } from './client'
// Must be registered as a global `functionMiddleware` in `src/start.ts`; otherwise
// the browser never attaches the bearer token to serverFn RPCs.
export const attachSupabaseAuth = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
const { data } = await supabase.auth.getSession()
const token = data.session?.access_token
return next({
headers: token ? { Authorization: `Bearer ${token}` } : {},
})
},
)
@@ -0,0 +1,109 @@
// This file is automatically generated. Do not edit it directly.
import { createMiddleware } from '@tanstack/react-start'
import { getRequest } from '@tanstack/react-start/server'
import { createClient } from '@supabase/supabase-js'
import type { Database } from './types'
function isNewSupabaseApiKey(value: string): boolean {
return value.startsWith('sb_publishable_') || value.startsWith('sb_secret_');
}
function createSupabaseFetch(supabaseKey: string): typeof fetch {
return (input, init) => {
const headers = new Headers(
typeof Request !== 'undefined' && input instanceof Request ? input.headers : undefined,
);
if (init?.headers) {
new Headers(init.headers).forEach((value, key) => headers.set(key, value));
}
// New Supabase API keys are opaque strings, not bearer JWTs.
if (isNewSupabaseApiKey(supabaseKey) && headers.get('Authorization') === `Bearer ${supabaseKey}`) {
headers.delete('Authorization');
}
headers.set('apikey', supabaseKey);
return fetch(input, { ...init, headers });
};
}
export const requireSupabaseAuth = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
const SUPABASE_URL = process.env.SUPABASE_URL;
const SUPABASE_PUBLISHABLE_KEY = process.env.SUPABASE_PUBLISHABLE_KEY;
if (!SUPABASE_URL || !SUPABASE_PUBLISHABLE_KEY) {
const missing = [
...(!SUPABASE_URL ? ['SUPABASE_URL'] : []),
...(!SUPABASE_PUBLISHABLE_KEY ? ['SUPABASE_PUBLISHABLE_KEY'] : []),
];
const message = `Missing Supabase environment variable(s): ${missing.join(', ')}. Connect Supabase in Lovable Cloud.`;
console.error(`[Supabase] ${message}`);
throw new Error(message);
}
const request = getRequest();
if (!request?.headers) {
throw new Error('Unauthorized: No request headers available');
}
const authHeader = request.headers.get('authorization');
if (!authHeader) {
throw new Error('Unauthorized: No authorization header provided');
}
if (!authHeader.startsWith('Bearer ')) {
throw new Error('Unauthorized: Only Bearer tokens are supported');
}
const token = authHeader.replace('Bearer ', '');
if (!token) {
throw new Error('Unauthorized: No token provided');
}
if (token.split('.').length !== 3) {
throw new Error('Unauthorized: Invalid token');
}
const supabase = createClient<Database>(
SUPABASE_URL!,
SUPABASE_PUBLISHABLE_KEY!,
{
global: {
fetch: createSupabaseFetch(SUPABASE_PUBLISHABLE_KEY!),
headers: {
Authorization: `Bearer ${token}`,
},
},
auth: {
storage: undefined,
persistSession: false,
autoRefreshToken: false,
},
}
);
const { data, error } = await supabase.auth.getClaims(token);
if (error || !data?.claims) {
throw new Error('Unauthorized: Invalid token');
}
if (!data.claims.sub) {
throw new Error('Unauthorized: No user ID found in token');
}
return next({
context: {
supabase,
userId: data.claims.sub,
claims: data.claims,
},
});
},
);
@@ -0,0 +1,69 @@
// This file is automatically generated. Do not edit it directly.
// Server-side Supabase client with service role key - bypasses RLS.
// Use this for admin operations in server functions and server routes only.
// For user-authenticated queries (with RLS), use the auth middleware instead.
import { createClient } from '@supabase/supabase-js';
import type { Database } from './types';
function isNewSupabaseApiKey(value: string): boolean {
return value.startsWith('sb_publishable_') || value.startsWith('sb_secret_');
}
function createSupabaseFetch(supabaseKey: string): typeof fetch {
return (input, init) => {
const headers = new Headers(
typeof Request !== 'undefined' && input instanceof Request ? input.headers : undefined,
);
if (init?.headers) {
new Headers(init.headers).forEach((value, key) => headers.set(key, value));
}
// New Supabase API keys are opaque strings, not bearer JWTs.
if (isNewSupabaseApiKey(supabaseKey) && headers.get('Authorization') === `Bearer ${supabaseKey}`) {
headers.delete('Authorization');
}
headers.set('apikey', supabaseKey);
return fetch(input, { ...init, headers });
};
}
function createSupabaseAdminClient() {
const SUPABASE_URL = process.env.SUPABASE_URL;
const SUPABASE_SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY;
if (!SUPABASE_URL || !SUPABASE_SERVICE_ROLE_KEY) {
const missing = [
...(!SUPABASE_URL ? ['SUPABASE_URL'] : []),
...(!SUPABASE_SERVICE_ROLE_KEY ? ['SUPABASE_SERVICE_ROLE_KEY'] : []),
];
const message = `Missing Supabase environment variable(s): ${missing.join(', ')}. Connect Supabase in Lovable Cloud.`;
console.error(`[Supabase] ${message}`);
throw new Error(message);
}
return createClient<Database>(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, {
global: {
fetch: createSupabaseFetch(SUPABASE_SERVICE_ROLE_KEY),
},
auth: {
storage: undefined,
persistSession: false,
autoRefreshToken: false,
}
});
}
let _supabaseAdmin: ReturnType<typeof createSupabaseAdminClient> | undefined;
// Server-side Supabase client with service role - bypasses RLS
// SECURITY: Only use this for trusted server-side operations, never expose to client code
// Load inside server handlers: const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
// Top-level import is safe only in other .server.ts modules - route files and *.functions.ts ship to the client bundle.
export const supabaseAdmin = new Proxy({} as ReturnType<typeof createSupabaseAdminClient>, {
get(_, prop, receiver) {
if (!_supabaseAdmin) _supabaseAdmin = createSupabaseAdminClient();
return Reflect.get(_supabaseAdmin, prop, receiver);
},
});
+68
View File
@@ -0,0 +1,68 @@
// This file is automatically generated. Do not edit it directly.
import { createClient } from '@supabase/supabase-js';
import type { Database } from './types';
function isNewSupabaseApiKey(value: string): boolean {
return value.startsWith('sb_publishable_') || value.startsWith('sb_secret_');
}
function createSupabaseFetch(supabaseKey: string): typeof fetch {
return (input, init) => {
const headers = new Headers(
typeof Request !== 'undefined' && input instanceof Request ? input.headers : undefined,
);
if (init?.headers) {
new Headers(init.headers).forEach((value, key) => headers.set(key, value));
}
// New Supabase API keys are opaque strings, not bearer JWTs.
if (isNewSupabaseApiKey(supabaseKey) && headers.get('Authorization') === `Bearer ${supabaseKey}`) {
headers.delete('Authorization');
}
headers.set('apikey', supabaseKey);
return fetch(input, { ...init, headers });
};
}
function createSupabaseClient() {
// Use import.meta.env for client-side (Vite build-time replacement)
// Fall back to process.env for SSR (server-side rendering)
const SUPABASE_URL = import.meta.env.VITE_SUPABASE_URL || process.env.SUPABASE_URL;
const SUPABASE_PUBLISHABLE_KEY = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY || process.env.SUPABASE_PUBLISHABLE_KEY;
if (!SUPABASE_URL || !SUPABASE_PUBLISHABLE_KEY) {
const missing = [
...(!SUPABASE_URL ? ['SUPABASE_URL'] : []),
...(!SUPABASE_PUBLISHABLE_KEY ? ['SUPABASE_PUBLISHABLE_KEY'] : []),
];
const message = `Missing Supabase environment variable(s): ${missing.join(', ')}. Connect Supabase in Lovable Cloud.`;
console.error(`[Supabase] ${message}`);
throw new Error(message);
}
return createClient<Database>(SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, {
global: {
fetch: createSupabaseFetch(SUPABASE_PUBLISHABLE_KEY),
},
auth: {
storage: typeof window !== 'undefined' ? localStorage : undefined,
persistSession: true,
autoRefreshToken: true,
}
});
}
let _supabase: ReturnType<typeof createSupabaseClient> | undefined;
// Import the supabase client like this:
// import { supabase } from "@/integrations/supabase/client";
export const supabase = new Proxy({} as ReturnType<typeof createSupabaseClient>, {
get(_, prop, receiver) {
if (!_supabase) _supabase = createSupabaseClient();
return Reflect.get(_supabase, prop, receiver);
},
});
+155
View File
@@ -0,0 +1,155 @@
export type Json =
| string
| number
| boolean
| null
| { [key: string]: Json | undefined }
| Json[]
export type Database = {
// Allows to automatically instantiate createClient with right options
// instead of createClient<Database, { PostgrestVersion: 'XX' }>(URL, KEY)
__InternalSupabase: {
PostgrestVersion: "14.5"
}
public: {
Tables: {
[_ in never]: never
}
Views: {
[_ in never]: never
}
Functions: {
[_ in never]: never
}
Enums: {
[_ in never]: never
}
CompositeTypes: {
[_ in never]: never
}
}
}
type DatabaseWithoutInternals = Omit<Database, "__InternalSupabase">
type DefaultSchema = DatabaseWithoutInternals[Extract<keyof Database, "public">]
export type Tables<
DefaultSchemaTableNameOrOptions extends
| keyof (DefaultSchema["Tables"] & DefaultSchema["Views"])
| { schema: keyof DatabaseWithoutInternals },
TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof DatabaseWithoutInternals
}
? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] &
DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])
: never = never,
> = DefaultSchemaTableNameOrOptions extends {
schema: keyof DatabaseWithoutInternals
}
? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] &
DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends {
Row: infer R
}
? R
: never
: DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] &
DefaultSchema["Views"])
? (DefaultSchema["Tables"] &
DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends {
Row: infer R
}
? R
: never
: never
export type TablesInsert<
DefaultSchemaTableNameOrOptions extends
| keyof DefaultSchema["Tables"]
| { schema: keyof DatabaseWithoutInternals },
TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof DatabaseWithoutInternals
}
? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"]
: never = never,
> = DefaultSchemaTableNameOrOptions extends {
schema: keyof DatabaseWithoutInternals
}
? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends {
Insert: infer I
}
? I
: never
: DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"]
? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends {
Insert: infer I
}
? I
: never
: never
export type TablesUpdate<
DefaultSchemaTableNameOrOptions extends
| keyof DefaultSchema["Tables"]
| { schema: keyof DatabaseWithoutInternals },
TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof DatabaseWithoutInternals
}
? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"]
: never = never,
> = DefaultSchemaTableNameOrOptions extends {
schema: keyof DatabaseWithoutInternals
}
? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends {
Update: infer U
}
? U
: never
: DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"]
? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends {
Update: infer U
}
? U
: never
: never
export type Enums<
DefaultSchemaEnumNameOrOptions extends
| keyof DefaultSchema["Enums"]
| { schema: keyof DatabaseWithoutInternals },
EnumName extends DefaultSchemaEnumNameOrOptions extends {
schema: keyof DatabaseWithoutInternals
}
? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"]
: never = never,
> = DefaultSchemaEnumNameOrOptions extends {
schema: keyof DatabaseWithoutInternals
}
? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName]
: DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"]
? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions]
: never
export type CompositeTypes<
PublicCompositeTypeNameOrOptions extends
| keyof DefaultSchema["CompositeTypes"]
| { schema: keyof DatabaseWithoutInternals },
CompositeTypeName extends PublicCompositeTypeNameOrOptions extends {
schema: keyof DatabaseWithoutInternals
}
? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"]
: never = never,
> = PublicCompositeTypeNameOrOptions extends {
schema: keyof DatabaseWithoutInternals
}
? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName]
: PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"]
? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions]
: never
export const Constants = {
public: {
Enums: {},
},
} as const
+2
View File
@@ -1,6 +1,7 @@
import { createStart, createMiddleware } from "@tanstack/react-start"; import { createStart, createMiddleware } from "@tanstack/react-start";
import { renderErrorPage } from "./lib/error-page"; import { renderErrorPage } from "./lib/error-page";
import { attachSupabaseAuth } from "@/integrations/supabase/auth-attacher";
const errorMiddleware = createMiddleware().server(async ({ next }) => { const errorMiddleware = createMiddleware().server(async ({ next }) => {
try { try {
@@ -18,5 +19,6 @@ const errorMiddleware = createMiddleware().server(async ({ next }) => {
}); });
export const startInstance = createStart(() => ({ export const startInstance = createStart(() => ({
functionMiddleware: [attachSupabaseAuth],
requestMiddleware: [errorMiddleware], requestMiddleware: [errorMiddleware],
})); }));
+1
View File
@@ -0,0 +1 @@
project_id = "fvjpfhfgkhgtwtzrgcgx"