--------------- PROFILES --------------- -- TABLE -- CREATE TABLE IF NOT EXISTS profiles ( -- ID id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), -- RELATIONSHIPS user_id UUID NOT NULL UNIQUE REFERENCES auth.users(id) ON DELETE CASCADE, -- METADATA created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ, -- REQUIRED bio TEXT NOT NULL CHECK (char_length(bio) <= 500), has_onboarded BOOLEAN NOT NULL DEFAULT FALSE, image_url TEXT NOT NULL CHECK (char_length(image_url) <= 1000), -- public file url in profile_images bucket image_path TEXT NOT NULL CHECK (char_length(image_path) <= 1000), -- file path in profile_images bucket profile_context TEXT NOT NULL CHECK (char_length(profile_context) <= 1500), display_name TEXT NOT NULL CHECK (char_length(display_name) <= 100), use_azure_openai BOOLEAN NOT NULL, username TEXT NOT NULL UNIQUE CHECK (char_length(username) >= 3 AND char_length(username) <= 25), -- OPTIONAL anthropic_api_key TEXT CHECK (char_length(anthropic_api_key) <= 1000), azure_openai_35_turbo_id TEXT CHECK (char_length(azure_openai_35_turbo_id) <= 1000), azure_openai_45_turbo_id TEXT CHECK (char_length(azure_openai_45_turbo_id) <= 1000), azure_openai_45_vision_id TEXT CHECK (char_length(azure_openai_45_vision_id) <= 1000), azure_openai_api_key TEXT CHECK (char_length(azure_openai_api_key) <= 1000), azure_openai_endpoint TEXT CHECK (char_length(azure_openai_endpoint) <= 1000), google_gemini_api_key TEXT CHECK (char_length(google_gemini_api_key) <= 1000), mistral_api_key TEXT CHECK (char_length(mistral_api_key) <= 1000), openai_api_key TEXT CHECK (char_length(openai_api_key) <= 1000), openai_organization_id TEXT CHECK (char_length(openai_organization_id) <= 1000), perplexity_api_key TEXT CHECK (char_length(perplexity_api_key) <= 1000) ); -- INDEXES -- CREATE INDEX idx_profiles_user_id ON profiles (user_id); -- RLS -- ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; CREATE POLICY "Allow full access to own profiles" ON profiles USING (user_id = auth.uid()) WITH CHECK (user_id = auth.uid()); -- FUNCTIONS -- CREATE OR REPLACE FUNCTION delete_old_profile_image() RETURNS TRIGGER LANGUAGE 'plpgsql' SECURITY DEFINER AS $$ DECLARE status INT; content TEXT; BEGIN IF TG_OP = 'DELETE' THEN SELECT INTO status, content result.status, result.content FROM public.delete_storage_object_from_bucket('profile_images', OLD.image_path) AS result; IF status <> 200 THEN RAISE WARNING 'Could not delete profile image: % %', status, content; END IF; END IF; IF TG_OP = 'DELETE' THEN RETURN OLD; END IF; RETURN NEW; END; $$; -- TRIGGERS -- CREATE TRIGGER update_profiles_updated_at BEFORE UPDATE ON profiles FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); CREATE OR REPLACE FUNCTION create_profile_and_workspace() RETURNS TRIGGER security definer set search_path = public AS $$ DECLARE random_username TEXT; BEGIN -- Generate a random username random_username := 'user' || substr(replace(gen_random_uuid()::text, '-', ''), 1, 16); -- Create a profile for the new user INSERT INTO public.profiles(user_id, anthropic_api_key, azure_openai_35_turbo_id, azure_openai_45_turbo_id, azure_openai_45_vision_id, azure_openai_api_key, azure_openai_endpoint, google_gemini_api_key, has_onboarded, image_url, image_path, mistral_api_key, display_name, bio, openai_api_key, openai_organization_id, perplexity_api_key, profile_context, use_azure_openai, username) VALUES( NEW.id, '', '', '', '', '', '', '', FALSE, '', '', '', '', '', '', '', '', '', FALSE, random_username ); -- Create the home workspace for the new user INSERT INTO public.workspaces(user_id, is_home, name, default_context_length, default_model, default_prompt, default_temperature, description, embeddings_provider, include_profile_context, include_workspace_instructions, instructions) VALUES( NEW.id, TRUE, 'Home', 4096, 'gpt-4-1106-preview', 'You are a friendly, helpful AI assistant.', 0.5, 'My home workspace.', 'openai', TRUE, TRUE, '' ); RETURN NEW; END; $$ language 'plpgsql'; CREATE TRIGGER create_profile_and_workspace_trigger AFTER INSERT ON auth.users FOR EACH ROW EXECUTE PROCEDURE public.create_profile_and_workspace(); CREATE TRIGGER delete_old_profile_image AFTER DELETE ON profiles FOR EACH ROW EXECUTE PROCEDURE delete_old_profile_image(); -- STORAGE -- INSERT INTO storage.buckets (id, name, public) VALUES ('profile_images', 'profile_images', true); CREATE POLICY "Allow public read access on profile images" ON storage.objects FOR SELECT USING (bucket_id = 'profile_images'); CREATE POLICY "Allow authenticated insert access to own profile images" ON storage.objects FOR INSERT TO authenticated WITH CHECK (bucket_id = 'profile_images' AND (storage.foldername(name))[1] = auth.uid()::text); CREATE POLICY "Allow authenticated update access to own profile images" ON storage.objects FOR UPDATE TO authenticated USING (bucket_id = 'profile_images' AND (storage.foldername(name))[1] = auth.uid()::text); CREATE POLICY "Allow authenticated delete access to own profile images" ON storage.objects FOR DELETE TO authenticated USING (bucket_id = 'profile_images' AND (storage.foldername(name))[1] = auth.uid()::text);