-- ============================================ -- TEMPLATE-SAAS: Core Functions -- Version: 1.0.0 -- ============================================ -- ============================================ -- TENANT CONTEXT FUNCTIONS -- ============================================ -- Set current tenant for RLS CREATE OR REPLACE FUNCTION auth.set_current_tenant(p_tenant_id UUID) RETURNS VOID AS $$ BEGIN PERFORM set_config('app.current_tenant_id', p_tenant_id::TEXT, FALSE); END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Get current tenant CREATE OR REPLACE FUNCTION auth.get_current_tenant() RETURNS UUID AS $$ BEGIN RETURN current_setting('app.current_tenant_id', TRUE)::UUID; EXCEPTION WHEN OTHERS THEN RETURN NULL; END; $$ LANGUAGE plpgsql STABLE; -- Set current user CREATE OR REPLACE FUNCTION auth.set_current_user(p_user_id UUID) RETURNS VOID AS $$ BEGIN PERFORM set_config('app.current_user_id', p_user_id::TEXT, FALSE); END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Get current user CREATE OR REPLACE FUNCTION auth.get_current_user() RETURNS UUID AS $$ BEGIN RETURN current_setting('app.current_user_id', TRUE)::UUID; EXCEPTION WHEN OTHERS THEN RETURN NULL; END; $$ LANGUAGE plpgsql STABLE; -- Clear context CREATE OR REPLACE FUNCTION auth.clear_context() RETURNS VOID AS $$ BEGIN PERFORM set_config('app.current_tenant_id', '', FALSE); PERFORM set_config('app.current_user_id', '', FALSE); END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- ============================================ -- PLAN & LIMITS FUNCTIONS -- ============================================ -- Get tenant's current plan limits CREATE OR REPLACE FUNCTION plans.get_tenant_limits(p_tenant_id UUID) RETURNS JSONB AS $$ DECLARE v_limits JSONB; BEGIN SELECT p.limits INTO v_limits FROM tenants.tenants t JOIN plans.plans p ON t.plan_id = p.id WHERE t.id = p_tenant_id; RETURN COALESCE(v_limits, '{}'::jsonb); END; $$ LANGUAGE plpgsql STABLE; -- Check if tenant can perform action based on limits CREATE OR REPLACE FUNCTION plans.check_limit( p_tenant_id UUID, p_limit_key VARCHAR, p_current_count INT ) RETURNS BOOLEAN AS $$ DECLARE v_limit INT; BEGIN SELECT (plans.get_tenant_limits(p_tenant_id)->>p_limit_key)::INT INTO v_limit; IF v_limit IS NULL THEN RETURN TRUE; -- No limit defined END IF; RETURN p_current_count < v_limit; END; $$ LANGUAGE plpgsql STABLE; -- Get tenant's feature flags CREATE OR REPLACE FUNCTION plans.get_tenant_features(p_tenant_id UUID) RETURNS JSONB AS $$ DECLARE v_features JSONB; BEGIN SELECT p.included_features INTO v_features FROM tenants.tenants t JOIN plans.plans p ON t.plan_id = p.id WHERE t.id = p_tenant_id; RETURN COALESCE(v_features, '[]'::jsonb); END; $$ LANGUAGE plpgsql STABLE; -- Check if tenant has feature CREATE OR REPLACE FUNCTION plans.has_feature( p_tenant_id UUID, p_feature_code VARCHAR ) RETURNS BOOLEAN AS $$ BEGIN RETURN plans.get_tenant_features(p_tenant_id) ? p_feature_code; END; $$ LANGUAGE plpgsql STABLE; -- ============================================ -- USER COUNT FUNCTIONS -- ============================================ -- Count active users in tenant CREATE OR REPLACE FUNCTION users.count_active_users(p_tenant_id UUID) RETURNS INT AS $$ BEGIN RETURN ( SELECT COUNT(*) FROM users.users WHERE tenant_id = p_tenant_id AND status = 'active' AND deleted_at IS NULL ); END; $$ LANGUAGE plpgsql STABLE; -- Check if tenant can add more users CREATE OR REPLACE FUNCTION users.can_add_user(p_tenant_id UUID) RETURNS BOOLEAN AS $$ BEGIN RETURN plans.check_limit( p_tenant_id, 'max_users', users.count_active_users(p_tenant_id) ); END; $$ LANGUAGE plpgsql STABLE; -- ============================================ -- UTILITY FUNCTIONS -- ============================================ -- Update updated_at column automatically CREATE OR REPLACE FUNCTION public.update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; -- ============================================================================= -- FUNCION: public.set_updated_at() -- Trigger function para actualizar columna updated_at automaticamente -- Usada por: mlm.structures, mlm.ranks, mlm.nodes (schema mlm) -- Nota: Equivalente a update_updated_at_column() - mantener ambas por compatibilidad -- ============================================================================= CREATE OR REPLACE FUNCTION public.set_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = CURRENT_TIMESTAMP; RETURN NEW; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION public.set_updated_at() IS 'Trigger function que actualiza automaticamente la columna updated_at al timestamp actual'; -- Create AI configs updated_at trigger (table defined in schemas/ai/) DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'ai' AND table_name = 'configs') THEN DROP TRIGGER IF EXISTS update_ai_configs_updated_at ON ai.configs; CREATE TRIGGER update_ai_configs_updated_at BEFORE UPDATE ON ai.configs FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); END IF; END $$; -- Create webhooks updated_at trigger (table defined in schemas/webhooks/) DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'webhooks' AND table_name = 'webhooks') THEN DROP TRIGGER IF EXISTS webhooks_updated_at ON webhooks.webhooks; CREATE TRIGGER webhooks_updated_at BEFORE UPDATE ON webhooks.webhooks FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); END IF; END $$; -- Generate slug from name CREATE OR REPLACE FUNCTION public.slugify(p_text VARCHAR) RETURNS VARCHAR AS $$ BEGIN RETURN LOWER( REGEXP_REPLACE( REGEXP_REPLACE( TRIM(p_text), '[^a-zA-Z0-9\s-]', '', 'g' ), '\s+', '-', 'g' ) ); END; $$ LANGUAGE plpgsql IMMUTABLE; -- Generate random token CREATE OR REPLACE FUNCTION public.generate_token(p_length INT DEFAULT 32) RETURNS VARCHAR AS $$ BEGIN RETURN encode(gen_random_bytes(p_length), 'hex'); END; $$ LANGUAGE plpgsql; -- Hash token CREATE OR REPLACE FUNCTION public.hash_token(p_token VARCHAR) RETURNS VARCHAR AS $$ BEGIN RETURN encode(digest(p_token, 'sha256'), 'hex'); END; $$ LANGUAGE plpgsql IMMUTABLE; -- Comments COMMENT ON FUNCTION auth.set_current_tenant IS 'Set tenant context for RLS policies'; COMMENT ON FUNCTION auth.get_current_tenant IS 'Get current tenant from context'; COMMENT ON FUNCTION plans.check_limit IS 'Check if action is within plan limits'; COMMENT ON FUNCTION plans.has_feature IS 'Check if tenant has access to feature';